apple-notes-mcp 2.8.15 → 2.8.17
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
|
@@ -39975,10 +39975,8 @@ import { homedir as homedir4 } from "os";
|
|
|
39975
39975
|
import { join as join4 } from "path";
|
|
39976
39976
|
var FIELD_SEP = "";
|
|
39977
39977
|
var RECORD_SEP = "";
|
|
39978
|
-
var AS_FIELD_SEP = "(
|
|
39979
|
-
var AS_RECORD_SEP = "(
|
|
39980
|
-
var AS_FIELD_SEP_LOCAL = "(character id 31)";
|
|
39981
|
-
var AS_RECORD_SEP_LOCAL = "(character id 30)";
|
|
39978
|
+
var AS_FIELD_SEP = "(character id 31)";
|
|
39979
|
+
var AS_RECORD_SEP = "(character id 30)";
|
|
39982
39980
|
var DEFAULT_EXPORT_PAGE_SIZE = 50;
|
|
39983
39981
|
var DEFAULT_EXPORT_MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
39984
39982
|
function exportMaxResponseBytes(env = process.env) {
|
|
@@ -40817,11 +40815,11 @@ var AppleNotesManager = class {
|
|
|
40817
40815
|
end try
|
|
40818
40816
|
${countGuard("noteIds")}
|
|
40819
40817
|
repeat with i from 1 to count of noteNames
|
|
40820
|
-
set end of resultList to (item i of noteNames) & ${
|
|
40818
|
+
set end of resultList to (item i of noteNames) & ${AS_FIELD_SEP} & (item i of noteIds)
|
|
40821
40819
|
end repeat
|
|
40822
40820
|
end if
|
|
40823
|
-
set AppleScript's text item delimiters to ${
|
|
40824
|
-
return (totalCount as text) & ${
|
|
40821
|
+
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
40822
|
+
return (totalCount as text) & ${AS_RECORD_SEP} & (resultList as text)
|
|
40825
40823
|
`;
|
|
40826
40824
|
}
|
|
40827
40825
|
const dateFetch = dateSetup ? `set noteDates to modification date of ${fullSource}
|
|
@@ -40838,9 +40836,9 @@ var AppleNotesManager = class {
|
|
|
40838
40836
|
${dateFetch}${countGuard("noteIds")}
|
|
40839
40837
|
${dateCountGuard}set resultList to {}
|
|
40840
40838
|
repeat with i from 1 to count of noteNames
|
|
40841
|
-
${dateGuardOpen}set end of resultList to (item i of noteNames) & ${
|
|
40839
|
+
${dateGuardOpen}set end of resultList to (item i of noteNames) & ${AS_FIELD_SEP} & (item i of noteIds)${dateGuardClose}
|
|
40842
40840
|
end repeat
|
|
40843
|
-
set AppleScript's text item delimiters to ${
|
|
40841
|
+
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
40844
40842
|
return resultList as text
|
|
40845
40843
|
`;
|
|
40846
40844
|
}
|
|
@@ -40975,11 +40973,11 @@ var AppleNotesManager = class {
|
|
|
40975
40973
|
if (item i of noteShared) is true then
|
|
40976
40974
|
set cd to item i of noteCreated
|
|
40977
40975
|
set md to item i of noteModified
|
|
40978
|
-
set end of resultList to (item i of noteNames) & ${
|
|
40976
|
+
set end of resultList to (item i of noteNames) & ${AS_FIELD_SEP} & (item i of noteIds) & ${AS_FIELD_SEP} & ${asDatePartsExpr("cd")} & ${AS_FIELD_SEP} & ${asDatePartsExpr("md")} & ${AS_FIELD_SEP} & "true" & ${AS_FIELD_SEP} & ((item i of noteLocked) as text)
|
|
40979
40977
|
end if
|
|
40980
40978
|
end repeat
|
|
40981
40979
|
end if
|
|
40982
|
-
set AppleScript's text item delimiters to ${
|
|
40980
|
+
set AppleScript's text item delimiters to ${AS_RECORD_SEP}
|
|
40983
40981
|
return resultList as text
|
|
40984
40982
|
`
|
|
40985
40983
|
);
|
|
@@ -42923,6 +42921,8 @@ function comparableVisibleText(html) {
|
|
|
42923
42921
|
var BACKGROUND_SHORTCUT = "Apple Notes MCP - Background Operations v5";
|
|
42924
42922
|
var backgroundStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT);
|
|
42925
42923
|
var nativeTagBridgeStatus = () => nativeTagsStatus();
|
|
42924
|
+
var MARKDOWN_NOTE_SHORTCUT = "Apple Notes MCP - Create Markdown Note";
|
|
42925
|
+
var markdownNoteStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_MARKDOWN_SHORTCUT || MARKDOWN_NOTE_SHORTCUT);
|
|
42926
42926
|
function readBackgroundSnapshot(manager, id2) {
|
|
42927
42927
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id2))
|
|
42928
42928
|
throw new Error("Exact note ID required");
|
|
@@ -42974,6 +42974,17 @@ var NATIVE_APPEND_ELEMENTS = [
|
|
|
42974
42974
|
];
|
|
42975
42975
|
var NATIVE_APPEND_SPAN_STYLE = /^font-size\s*:\s*\d{1,3}(?:\.\d+)?(?:px|pt)\s*;?$/i;
|
|
42976
42976
|
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.`;
|
|
42977
|
+
var UNDERSCORES_OUTSIDE_A_WORD = "underscores outside a word";
|
|
42978
|
+
var UNMODELED_MARKDOWN = [
|
|
42979
|
+
[/(?<![\p{L}\p{N}])_|_(?![\p{L}\p{N}])/mu, UNDERSCORES_OUTSIDE_A_WORD],
|
|
42980
|
+
[/\\[!-/:-@[-`{-~]/m, "backslash escapes"],
|
|
42981
|
+
[/&(?:#\d+|#x[0-9a-f]+|[a-z][a-z0-9]*);/im, "character references"],
|
|
42982
|
+
[/^ {0,3}(?:=+|-+|(?:\*[ \t]*){3,})[ \t]*$/m, "underline or rule lines"],
|
|
42983
|
+
[/^ {1,3}(?:#|[-+*][ \t]|\d+[.)][ \t])/m, "indented headings or list items"],
|
|
42984
|
+
[/^\d+\)[ \t]/m, "`1)` lists"],
|
|
42985
|
+
[/^#{1,3}[ \t].*[ \t]#+[ \t]*$/m, "closing # sequences"],
|
|
42986
|
+
[/\[[^\]\n]*[*_][^\]\n]*\]\(/m, "formatting inside link labels"]
|
|
42987
|
+
];
|
|
42977
42988
|
function validateAppendContent(content, format) {
|
|
42978
42989
|
if (!content || content.length > 1024 * 1024 || content.includes("\0"))
|
|
42979
42990
|
throw new Error("Invalid append content (limit 1 MiB)");
|
|
@@ -43003,11 +43014,18 @@ function validateAppendContent(content, format) {
|
|
|
43003
43014
|
}
|
|
43004
43015
|
if (/<!--|<!|<\?|<[^>]*$/u.test(content)) throw new Error("Unsupported HTML markup");
|
|
43005
43016
|
}
|
|
43006
|
-
if (format === "markdown"
|
|
43007
|
-
|
|
43017
|
+
if (format === "markdown") {
|
|
43018
|
+
if (/!\[|<\/?[a-z]|\]\(\s*(?:javascript|data|file):/i.test(content))
|
|
43019
|
+
throw new Error("Markdown images, raw HTML and local/executable links are unsupported");
|
|
43020
|
+
const withoutLinkDestinations = content.replace(/(\[[^\]\n]+\])\([^()\s]+\)/g, "$1()");
|
|
43021
|
+
for (const [pattern, name] of UNMODELED_MARKDOWN)
|
|
43022
|
+
if (pattern.test(name === UNDERSCORES_OUTSIDE_A_WORD ? withoutLinkDestinations : content))
|
|
43023
|
+
throw new Error(
|
|
43024
|
+
`Markdown cannot use ${name}; Notes would change that text, so the result could not be verified`
|
|
43025
|
+
);
|
|
43026
|
+
}
|
|
43008
43027
|
}
|
|
43009
|
-
function runBackgroundShortcut(input) {
|
|
43010
|
-
const status = backgroundStatus();
|
|
43028
|
+
function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
43011
43029
|
if (!status.installed)
|
|
43012
43030
|
throw new Error(
|
|
43013
43031
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
@@ -43109,6 +43127,11 @@ function assertPreserved(before, after, options = {}) {
|
|
|
43109
43127
|
}
|
|
43110
43128
|
}
|
|
43111
43129
|
}
|
|
43130
|
+
function describeTransportFailure(error2) {
|
|
43131
|
+
const detail = error2;
|
|
43132
|
+
const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
|
|
43133
|
+
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)}`;
|
|
43134
|
+
}
|
|
43112
43135
|
function mutateBackground(request, operation, data, verify, deps) {
|
|
43113
43136
|
if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
|
|
43114
43137
|
throw new Error("Use a distinctive existing single-line scope of 12\u2013500 characters");
|
|
@@ -43128,9 +43151,7 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
43128
43151
|
deps.run({ ...data, operation, title: before.title, scopeText: request.scopeText });
|
|
43129
43152
|
} catch (error2) {
|
|
43130
43153
|
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)}`;
|
|
43154
|
+
transportMessage = describeTransportFailure(error2);
|
|
43134
43155
|
}
|
|
43135
43156
|
const after = deps.read(request.id);
|
|
43136
43157
|
try {
|
|
@@ -43218,6 +43239,109 @@ function setNativeTag(manager, request) {
|
|
|
43218
43239
|
deps
|
|
43219
43240
|
);
|
|
43220
43241
|
}
|
|
43242
|
+
var literalMarkdown = (text) => text.replace(/[!-/:-@[-`{-~]/g, "\\$&");
|
|
43243
|
+
function headingLevels(html) {
|
|
43244
|
+
return [...html.matchAll(/<h([1-3])\b[^>]*>([\s\S]*?)<\/h\1>/gi)].filter((match) => comparableVisibleText(match[2])).map((match) => Number(match[1]));
|
|
43245
|
+
}
|
|
43246
|
+
function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
43247
|
+
if (/[\r\n\0]/u.test(request.title)) throw new Error("A Markdown note title must be one line");
|
|
43248
|
+
validateAppendContent(request.content, "markdown");
|
|
43249
|
+
const bodyHtml = appendMarkdownHtml(request.content);
|
|
43250
|
+
if (request.folder) buildFolderReference(request.folder);
|
|
43251
|
+
const expectedText = `${request.title} ${comparableVisibleText(bodyHtml)}`.replace(/\s+/gu, " ").trim();
|
|
43252
|
+
const expectedLevels = [1, ...headingLevels(bodyHtml)].join();
|
|
43253
|
+
const status = markdownNoteStatus();
|
|
43254
|
+
if (!status.installed)
|
|
43255
|
+
throw new Error(
|
|
43256
|
+
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
43257
|
+
);
|
|
43258
|
+
const segments = (path4) => JSON.stringify(splitFolderPath(path4).map((part) => part.toLocaleLowerCase()));
|
|
43259
|
+
if (request.folder) {
|
|
43260
|
+
const wanted = segments(request.folder);
|
|
43261
|
+
if (!manager.listAccounts().some(
|
|
43262
|
+
(account) => account.defaultFolder && manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
|
|
43263
|
+
))
|
|
43264
|
+
throw new Error(
|
|
43265
|
+
`Folder "${request.folder}" does not exist; create it with create-folder first. Nothing was created`
|
|
43266
|
+
);
|
|
43267
|
+
}
|
|
43268
|
+
const defaultFolderNotes = () => new Map(
|
|
43269
|
+
manager.listAccounts().flatMap(
|
|
43270
|
+
(account) => account.defaultFolder ? manager.listNoteRefs(account.name, account.defaultFolder).map((note) => [note.id, account.name]) : []
|
|
43271
|
+
)
|
|
43272
|
+
);
|
|
43273
|
+
const before = defaultFolderNotes();
|
|
43274
|
+
let transportMessage = "";
|
|
43275
|
+
try {
|
|
43276
|
+
run(
|
|
43277
|
+
{
|
|
43278
|
+
operation: "create-markdown",
|
|
43279
|
+
text: `# ${literalMarkdown(request.title)}
|
|
43280
|
+
|
|
43281
|
+
${request.content}`
|
|
43282
|
+
},
|
|
43283
|
+
status
|
|
43284
|
+
);
|
|
43285
|
+
} catch (error2) {
|
|
43286
|
+
transportMessage = describeTransportFailure(error2);
|
|
43287
|
+
}
|
|
43288
|
+
const reason = (error2) => error2 instanceof Error ? error2.message : String(error2);
|
|
43289
|
+
let candidates = [];
|
|
43290
|
+
try {
|
|
43291
|
+
const created = [...defaultFolderNotes()].filter(([noteId3]) => !before.has(noteId3));
|
|
43292
|
+
candidates = created.map(([noteId3]) => noteId3);
|
|
43293
|
+
const failures = [];
|
|
43294
|
+
const verified = created.flatMap(([noteId3, account2]) => {
|
|
43295
|
+
try {
|
|
43296
|
+
const note2 = readBackgroundSnapshot(manager, noteId3);
|
|
43297
|
+
if (note2.rich.text.replace(/[\s\ufffc]+/gu, " ").trim() !== expectedText)
|
|
43298
|
+
throw new Error("Note text not verified");
|
|
43299
|
+
if (headingLevels(note2.html).join() !== expectedLevels)
|
|
43300
|
+
throw new Error("Heading styles not verified");
|
|
43301
|
+
assertAppendedHtmlLinks(0, note2.rich.links, bodyHtml);
|
|
43302
|
+
return [{ id: noteId3, account: account2, note: note2 }];
|
|
43303
|
+
} catch (error2) {
|
|
43304
|
+
failures.push(`${noteId3}: ${reason(error2)}`);
|
|
43305
|
+
return [];
|
|
43306
|
+
}
|
|
43307
|
+
});
|
|
43308
|
+
if (verified.length !== 1)
|
|
43309
|
+
throw new Error(
|
|
43310
|
+
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"
|
|
43311
|
+
);
|
|
43312
|
+
const { id: id2, account } = verified[0];
|
|
43313
|
+
candidates = [id2];
|
|
43314
|
+
let { note } = verified[0];
|
|
43315
|
+
if (request.folder) {
|
|
43316
|
+
const wanted = segments(request.folder);
|
|
43317
|
+
if (!manager.listFolders(account).some((folder) => segments(folder.name) === wanted))
|
|
43318
|
+
throw new Error(
|
|
43319
|
+
`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`
|
|
43320
|
+
);
|
|
43321
|
+
if (!manager.moveNoteById(id2, request.folder, account))
|
|
43322
|
+
throw new Error(
|
|
43323
|
+
`created and verified in the ${account} default folder, but not moved to "${request.folder}"; use move-note instead of creating it again`
|
|
43324
|
+
);
|
|
43325
|
+
note = readBackgroundSnapshot(manager, id2);
|
|
43326
|
+
}
|
|
43327
|
+
return {
|
|
43328
|
+
ok: true,
|
|
43329
|
+
id: id2,
|
|
43330
|
+
title: request.title,
|
|
43331
|
+
folder: request.folder,
|
|
43332
|
+
account,
|
|
43333
|
+
contentHash: note.hash,
|
|
43334
|
+
verified: true,
|
|
43335
|
+
...transportMessage ? {
|
|
43336
|
+
transportWarning: "Transport was uncertain; exact-ID readback verified the requested result"
|
|
43337
|
+
} : {}
|
|
43338
|
+
};
|
|
43339
|
+
} catch (error2) {
|
|
43340
|
+
throw new Error(
|
|
43341
|
+
`Operation outcome uncertain; ${candidates.length ? `read ${candidates.length === 1 ? "note" : "notes"} ${candidates.join(", ")}` : "search for the title"} before any retry: ${reason(error2)}${transportMessage ? `; ${transportMessage}` : ""}`
|
|
43342
|
+
);
|
|
43343
|
+
}
|
|
43344
|
+
}
|
|
43221
43345
|
|
|
43222
43346
|
// src/tools/doctor.ts
|
|
43223
43347
|
function runDoctor(manager) {
|
|
@@ -43256,10 +43380,11 @@ function runDoctor(manager) {
|
|
|
43256
43380
|
(name) => nativeTagsStatus(name)
|
|
43257
43381
|
);
|
|
43258
43382
|
const missing = bridgeStatuses.filter((status) => !status.installed);
|
|
43383
|
+
const markdown = markdownBridgeDetail();
|
|
43259
43384
|
checks.push({
|
|
43260
43385
|
name: "Native write Shortcuts",
|
|
43261
43386
|
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}`
|
|
43387
|
+
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
43388
|
});
|
|
43264
43389
|
} catch (error2) {
|
|
43265
43390
|
checks.push({
|
|
@@ -43272,6 +43397,14 @@ function runDoctor(manager) {
|
|
|
43272
43397
|
const healthy = !checks.some((c) => c.status === "fail");
|
|
43273
43398
|
return { healthy, checks };
|
|
43274
43399
|
}
|
|
43400
|
+
function markdownBridgeDetail() {
|
|
43401
|
+
const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
|
|
43402
|
+
try {
|
|
43403
|
+
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).`;
|
|
43404
|
+
} catch (error2) {
|
|
43405
|
+
return `Optional ${MARKDOWN_NOTE_SHORTCUT} bridge: could not inspect (${purpose}): ${String(error2)}.`;
|
|
43406
|
+
}
|
|
43407
|
+
}
|
|
43275
43408
|
function checkNodeRuntimeSignature() {
|
|
43276
43409
|
const name = "Node runtime signature";
|
|
43277
43410
|
try {
|
|
@@ -43894,7 +44027,8 @@ var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
|
|
|
43894
44027
|
"insert-note-link",
|
|
43895
44028
|
"set-note-pinned",
|
|
43896
44029
|
"remove-native-tags",
|
|
43897
|
-
"replace-native-tag"
|
|
44030
|
+
"replace-native-tag",
|
|
44031
|
+
"create-note-markdown"
|
|
43898
44032
|
]);
|
|
43899
44033
|
var LIVE_VALIDATION_BLOCKERS = {};
|
|
43900
44034
|
var signingRefusal = "Installed Shortcuts refuses to sign this Notes action (unsupported features); no background fallback is enabled";
|
|
@@ -43982,16 +44116,24 @@ function registerNativeOperations(server2, manager) {
|
|
|
43982
44116
|
"set-note-pinned",
|
|
43983
44117
|
"remove-native-tags",
|
|
43984
44118
|
"replace-native-tag",
|
|
43985
|
-
"insert-note-link"
|
|
44119
|
+
"insert-note-link",
|
|
44120
|
+
"create-note-markdown"
|
|
43986
44121
|
];
|
|
43987
44122
|
let tagBridgeInstalled = false;
|
|
43988
44123
|
try {
|
|
43989
44124
|
tagBridgeInstalled = nativeTagBridgeStatus().installed;
|
|
43990
44125
|
} catch {
|
|
43991
44126
|
}
|
|
44127
|
+
let markdownBridgeInstalled = false;
|
|
44128
|
+
try {
|
|
44129
|
+
markdownBridgeInstalled = markdownNoteStatus().installed;
|
|
44130
|
+
} catch {
|
|
44131
|
+
}
|
|
44132
|
+
const installed = (name) => name === "create-note-markdown" ? markdownBridgeInstalled : bridge.installed;
|
|
43992
44133
|
return {
|
|
43993
44134
|
bridge,
|
|
43994
44135
|
nativeTagBridgeInstalled: tagBridgeInstalled,
|
|
44136
|
+
markdownNoteBridgeInstalled: markdownBridgeInstalled,
|
|
43995
44137
|
mode: "background-only",
|
|
43996
44138
|
operations: Object.fromEntries(
|
|
43997
44139
|
native.map((name) => [
|
|
@@ -43999,8 +44141,8 @@ function registerNativeOperations(server2, manager) {
|
|
|
43999
44141
|
{
|
|
44000
44142
|
implemented: true,
|
|
44001
44143
|
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" :
|
|
44144
|
+
available: VERIFIED_BACKGROUND.has(name) && installed(name) && (name !== "replace-native-tag" || tagBridgeInstalled),
|
|
44145
|
+
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
44146
|
}
|
|
44005
44147
|
])
|
|
44006
44148
|
),
|
|
@@ -44217,13 +44359,21 @@ function registerNativeOperations(server2, manager) {
|
|
|
44217
44359
|
// src/setupShortcuts.ts
|
|
44218
44360
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
44219
44361
|
import { existsSync as existsSync7 } from "node:fs";
|
|
44362
|
+
import { release } from "node:os";
|
|
44220
44363
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
44221
44364
|
import { fileURLToPath } from "node:url";
|
|
44365
|
+
var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
|
|
44366
|
+
var MARKDOWN_MIN_DARWIN_MAJOR = 25;
|
|
44222
44367
|
var shortcutFiles = [
|
|
44223
44368
|
{ name: NATIVE_TAGS_SHORTCUT, file: "Apple Notes MCP - Native Tags.shortcut" },
|
|
44224
44369
|
{
|
|
44225
44370
|
name: BACKGROUND_SHORTCUT,
|
|
44226
44371
|
file: "Apple Notes MCP - Background Operations v5.shortcut"
|
|
44372
|
+
},
|
|
44373
|
+
{
|
|
44374
|
+
name: MARKDOWN_NOTE_SHORTCUT,
|
|
44375
|
+
file: "Apple Notes MCP - Create Markdown Note.shortcut",
|
|
44376
|
+
optional: true
|
|
44227
44377
|
}
|
|
44228
44378
|
];
|
|
44229
44379
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
@@ -44234,7 +44384,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
44234
44384
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
44235
44385
|
});
|
|
44236
44386
|
const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
44237
|
-
const
|
|
44387
|
+
const osRelease = (dependencies.osRelease || release)();
|
|
44388
|
+
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
44389
|
+
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
44238
44390
|
const path4 = resolve2(baseDirectory, file);
|
|
44239
44391
|
let installed = false;
|
|
44240
44392
|
let identifier;
|
|
@@ -44247,27 +44399,41 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
44247
44399
|
error2 = cause instanceof Error ? cause.message : String(cause);
|
|
44248
44400
|
}
|
|
44249
44401
|
let opened = false;
|
|
44402
|
+
let skipped;
|
|
44250
44403
|
if (!installed && !checkOnly) {
|
|
44251
|
-
if (!
|
|
44404
|
+
if (optional2 && !(darwinMajor >= MARKDOWN_MIN_DARWIN_MAJOR))
|
|
44405
|
+
skipped = `requires macOS 26 or later (this Mac reports Darwin ${osRelease})`;
|
|
44406
|
+
else if (!exists(path4)) error2 = `Packaged Shortcut is missing: ${path4}`;
|
|
44252
44407
|
else {
|
|
44253
44408
|
const result = open(path4);
|
|
44254
44409
|
opened = result.ok;
|
|
44255
44410
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
44256
44411
|
}
|
|
44257
44412
|
}
|
|
44258
|
-
return {
|
|
44413
|
+
return {
|
|
44414
|
+
name,
|
|
44415
|
+
installed,
|
|
44416
|
+
identifier,
|
|
44417
|
+
file: path4,
|
|
44418
|
+
opened,
|
|
44419
|
+
...optional2 ? { optional: optional2 } : {},
|
|
44420
|
+
...skipped ? { skipped } : {},
|
|
44421
|
+
...error2 ? { error: error2 } : {}
|
|
44422
|
+
};
|
|
44259
44423
|
});
|
|
44260
|
-
return { ready: items.every((item) => item.installed), checkOnly, items };
|
|
44424
|
+
return { ready: items.every((item) => item.optional || item.installed), checkOnly, items };
|
|
44261
44425
|
}
|
|
44262
44426
|
function formatShortcutSetup(report) {
|
|
44263
44427
|
const lines = ["Apple Notes MCP Shortcut setup", ""];
|
|
44264
44428
|
for (const item of report.items) {
|
|
44265
|
-
|
|
44266
|
-
|
|
44267
|
-
else lines.push(`\
|
|
44429
|
+
const note = item.optional ? ` ${OPTIONAL_BRIDGE_NOTE}` : "";
|
|
44430
|
+
if (item.installed) lines.push(`\u2713 ${item.name} (${item.identifier})${note}`);
|
|
44431
|
+
else if (item.opened) lines.push(`\u2192 ${item.name}: confirm \u201CAdd Shortcut\u201D in macOS${note}`);
|
|
44432
|
+
else if (item.skipped) lines.push(`\u2013 ${item.name}: skipped, ${item.skipped}${note}`);
|
|
44433
|
+
else lines.push(`\u2717 ${item.name}: ${item.error || "not installed"}${note}`);
|
|
44268
44434
|
}
|
|
44269
44435
|
lines.push("");
|
|
44270
|
-
if (report.ready) lines.push("Both Shortcut bridges are installed.");
|
|
44436
|
+
if (report.ready) lines.push("Both required Shortcut bridges are installed.");
|
|
44271
44437
|
else if (report.checkOnly) lines.push("Run `apple-notes-mcp setup` to open missing workflows.");
|
|
44272
44438
|
else
|
|
44273
44439
|
lines.push(
|
|
@@ -44383,9 +44549,11 @@ registerTool(
|
|
|
44383
44549
|
content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).describe(
|
|
44384
44550
|
'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
44551
|
),
|
|
44386
|
-
format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe(
|
|
44552
|
+
format: external_exports.enum(["plaintext", "html", "markdown"]).optional().default("plaintext").describe(
|
|
44553
|
+
"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)"
|
|
44554
|
+
),
|
|
44387
44555
|
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."
|
|
44556
|
+
"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
44557
|
),
|
|
44390
44558
|
folder: external_exports.string().max(MAX.FOLDER).optional().describe(
|
|
44391
44559
|
"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 +44573,19 @@ registerTool(
|
|
|
44405
44573
|
}
|
|
44406
44574
|
},
|
|
44407
44575
|
withErrorHandling(({ title, content, format = "plaintext", tags = [], folder, account }) => {
|
|
44576
|
+
if (format === "markdown") {
|
|
44577
|
+
if (account)
|
|
44578
|
+
return errorResponse(
|
|
44579
|
+
"Markdown notes are created in the iCloud account, the only one where Notes interprets Markdown; omit account"
|
|
44580
|
+
);
|
|
44581
|
+
if (tags.length)
|
|
44582
|
+
return errorResponse(
|
|
44583
|
+
'tags are not supported with format "markdown"; create the note without tags, then add them with add-native-tags using the returned id'
|
|
44584
|
+
);
|
|
44585
|
+
requireValidated("create-note-markdown");
|
|
44586
|
+
const result = createMarkdownNote(notesManager, { title, content, folder });
|
|
44587
|
+
return successResponse(`Note created from Markdown: "${title}" [id: ${result.id}]`, result);
|
|
44588
|
+
}
|
|
44408
44589
|
const note = notesManager.createNote(title, content, tags, folder, account, format);
|
|
44409
44590
|
if (!note) {
|
|
44410
44591
|
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
|
+
```
|