apple-notes-mcp 2.8.14 → 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,18 +59,36 @@ 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
|
|
|
79
|
+
After install **and after every upgrade**, open Shortcuts.app and run each
|
|
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
|
|
86
|
+
Shortcuts in the background, where Shortcuts cannot display a first-run consent
|
|
87
|
+
prompt: an unanswered one stalls every native write on that bridge until it
|
|
88
|
+
times out, while `doctor` still reports the bridge installed. Quitting or
|
|
89
|
+
relaunching Shortcuts.app or Notes.app does not clear it; the foreground run
|
|
90
|
+
does, once per bridge.
|
|
91
|
+
|
|
74
92
|
### Using the Codex Marketplace
|
|
75
93
|
|
|
76
94
|
The same plugin is available for Codex. Add the marketplace and install the plugin:
|
|
@@ -192,10 +210,10 @@ Creates a new note in Apple Notes.
|
|
|
192
210
|
|-----------|------|----------|-------------|
|
|
193
211
|
| `title` | string | Yes | The title of the note. Automatically prepended as `<h1>` — do NOT include the title in `content` |
|
|
194
212
|
| `content` | string | Yes | The body content of the note (do not repeat the title here) |
|
|
195
|
-
| `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"` |
|
|
196
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 |
|
|
197
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) |
|
|
198
|
-
| `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) |
|
|
199
217
|
|
|
200
218
|
**Example (tagged with inline hashtags):**
|
|
201
219
|
```json
|
|
@@ -225,13 +243,63 @@ Creates a new note in Apple Notes.
|
|
|
225
243
|
|
|
226
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.
|
|
227
245
|
|
|
228
|
-
> **Known limitation:** `create-note` sets the note
|
|
229
|
-
> `body` property, which does not apply real Notes
|
|
230
|
-
> content — an `<h2>`/`<h3>` tag or a
|
|
231
|
-
> in `content` renders as plain bold,
|
|
232
|
-
>
|
|
233
|
-
>
|
|
234
|
-
>
|
|
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).
|
|
235
303
|
|
|
236
304
|
**Returns:** Confirmation message with note title and ID. Save the ID for subsequent operations like `update-note`, `delete-note`, etc.
|
|
237
305
|
|
|
@@ -347,7 +415,10 @@ Access.
|
|
|
347
415
|
#### `native-tags-status`
|
|
348
416
|
|
|
349
417
|
Checks whether exactly one configured Native Tags Shortcut is installed. An
|
|
350
|
-
installed workflow may still need macOS permission on its first execution
|
|
418
|
+
installed workflow may still need macOS permission on its first execution, and
|
|
419
|
+
installation does not show whether that consent was given: run it once in the
|
|
420
|
+
foreground in Shortcuts.app and choose **Always Allow** after install or upgrade
|
|
421
|
+
(see [Troubleshooting](#native-writes-time-out-or-report-an-uncertain-outcome)).
|
|
351
422
|
|
|
352
423
|
---
|
|
353
424
|
|
|
@@ -1056,10 +1127,15 @@ Lists all notes shared with collaborators.
|
|
|
1056
1127
|
### Native background operations
|
|
1057
1128
|
|
|
1058
1129
|
Install the signed workflows once as described in
|
|
1059
|
-
[`shortcuts/README.md`](shortcuts/README.md)
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1130
|
+
[`shortcuts/README.md`](shortcuts/README.md), then run each once in the
|
|
1131
|
+
foreground in Shortcuts.app and choose **Always Allow** — again after every
|
|
1132
|
+
upgrade — so no first-run consent prompt is left for a background run that
|
|
1133
|
+
cannot display it. Native writes never use UI
|
|
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"`.
|
|
1063
1139
|
|
|
1064
1140
|
#### `get-capabilities`
|
|
1065
1141
|
|
|
@@ -1079,9 +1155,14 @@ subset as `format: "html"`'s Markdown-shaped content (`#`/`##`/`###` headings,
|
|
|
1079
1155
|
flat lists, emphasis, and inline links) but is sent through Notes' own native
|
|
1080
1156
|
Markdown importer rather than converted to HTML first, so `#`/`##`/`###`
|
|
1081
1157
|
produce real Title/Heading/Subheading — Notes' HTML importer only
|
|
1082
|
-
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
|
|
1083
1162
|
transport failure names the Shortcut it was waiting on, so a missing or
|
|
1084
|
-
unapproved bridge is identified rather than guessed at
|
|
1163
|
+
unapproved bridge is identified rather than guessed at; a timeout also says it
|
|
1164
|
+
may be an unanswered first-run consent prompt and names the Shortcut to run once
|
|
1165
|
+
in the foreground.
|
|
1085
1166
|
|
|
1086
1167
|
#### `create-checklist-item`
|
|
1087
1168
|
|
|
@@ -1396,6 +1477,12 @@ In a JSON string literal the two characters `\\` denote **one** literal backslas
|
|
|
1396
1477
|
- Go to System Settings > Privacy & Security > Automation
|
|
1397
1478
|
- Ensure your terminal/Claude has permission to control Notes
|
|
1398
1479
|
|
|
1480
|
+
### Native writes time out or report an uncertain outcome
|
|
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
|
|
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
|
|
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
|
|
1484
|
+
- Read the exact note before retrying — the write may have landed
|
|
1485
|
+
|
|
1399
1486
|
### "Note not found"
|
|
1400
1487
|
- Note titles must match exactly (case-sensitive)
|
|
1401
1488
|
- Check if the note is in a different account
|
package/build/index.js
CHANGED
|
@@ -42736,6 +42736,14 @@ import { execFileSync as execFileSync6 } from "node:child_process";
|
|
|
42736
42736
|
import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
|
|
42737
42737
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
42738
42738
|
import { join as join7 } from "node:path";
|
|
42739
|
+
|
|
42740
|
+
// src/services/shortcutConsent.ts
|
|
42741
|
+
function shortcutConsentHint(shortcut) {
|
|
42742
|
+
const target = shortcut ? `"${shortcut}"` : "the bridge Shortcut";
|
|
42743
|
+
return `This may be an unanswered first-run Shortcuts consent prompt, which a background run cannot display: run ${target} once in the foreground in Shortcuts.app and choose Always Allow.`;
|
|
42744
|
+
}
|
|
42745
|
+
|
|
42746
|
+
// src/services/nativeTags.ts
|
|
42739
42747
|
var NATIVE_TAGS_SHORTCUT = "Apple Notes MCP - Native Tags";
|
|
42740
42748
|
function normalizeNativeTags(tags) {
|
|
42741
42749
|
if (!tags.length || tags.length > 100) throw new Error("Provide between 1 and 100 tags");
|
|
@@ -42790,7 +42798,7 @@ function addNativeTags(request, deps) {
|
|
|
42790
42798
|
};
|
|
42791
42799
|
if (after.title !== before.title || allTags.some((tag) => !after.rich.nativeTags.includes(tag)) || textWithoutTags(before.rich.text) !== textWithoutTags(after.rich.text) || linkSignature(before.rich.links) !== linkSignature(after.rich.links) || before.rich.nativeObjectIds.some((id2) => !after.rich.nativeObjectIds.includes(id2)) || before.rich.hasChecklist !== after.rich.hasChecklist) {
|
|
42792
42800
|
throw new Error(
|
|
42793
|
-
"Shortcuts ran, but exact-ID tags/text/links readback was not verified. Read the note before any retry"
|
|
42801
|
+
transportWarning ? `Shortcuts did not complete cleanly, and exact-ID tags/text/links readback was not verified. Read the note before any retry. ${transportWarning}` : "Shortcuts ran, but exact-ID tags/text/links readback was not verified. Read the note before any retry"
|
|
42794
42802
|
);
|
|
42795
42803
|
}
|
|
42796
42804
|
return {
|
|
@@ -42833,9 +42841,12 @@ function runNativeTagsShortcut(input) {
|
|
|
42833
42841
|
maxBuffer: 1024 * 1024,
|
|
42834
42842
|
stdio: ["ignore", "pipe", "pipe"]
|
|
42835
42843
|
});
|
|
42836
|
-
} catch {
|
|
42837
|
-
throw
|
|
42838
|
-
|
|
42844
|
+
} catch (error2) {
|
|
42845
|
+
throw Object.assign(
|
|
42846
|
+
new Error(
|
|
42847
|
+
`Native tag operation did not complete cleanly. ${shortcutConsentHint(status.shortcut)} Do not retry automatically; read the exact note first`
|
|
42848
|
+
),
|
|
42849
|
+
{ shortcut: status.shortcut, code: error2?.code }
|
|
42839
42850
|
);
|
|
42840
42851
|
} finally {
|
|
42841
42852
|
rmSync2(directory, { recursive: true, force: true });
|
|
@@ -42912,6 +42923,8 @@ function comparableVisibleText(html) {
|
|
|
42912
42923
|
var BACKGROUND_SHORTCUT = "Apple Notes MCP - Background Operations v5";
|
|
42913
42924
|
var backgroundStatus = () => nativeTagsStatus(process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT);
|
|
42914
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);
|
|
42915
42928
|
function readBackgroundSnapshot(manager, id2) {
|
|
42916
42929
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id2))
|
|
42917
42930
|
throw new Error("Exact note ID required");
|
|
@@ -42963,6 +42976,17 @@ var NATIVE_APPEND_ELEMENTS = [
|
|
|
42963
42976
|
];
|
|
42964
42977
|
var NATIVE_APPEND_SPAN_STYLE = /^font-size\s*:\s*\d{1,3}(?:\.\d+)?(?:px|pt)\s*;?$/i;
|
|
42965
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
|
+
];
|
|
42966
42990
|
function validateAppendContent(content, format) {
|
|
42967
42991
|
if (!content || content.length > 1024 * 1024 || content.includes("\0"))
|
|
42968
42992
|
throw new Error("Invalid append content (limit 1 MiB)");
|
|
@@ -42992,11 +43016,18 @@ function validateAppendContent(content, format) {
|
|
|
42992
43016
|
}
|
|
42993
43017
|
if (/<!--|<!|<\?|<[^>]*$/u.test(content)) throw new Error("Unsupported HTML markup");
|
|
42994
43018
|
}
|
|
42995
|
-
if (format === "markdown"
|
|
42996
|
-
|
|
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
|
+
}
|
|
42997
43029
|
}
|
|
42998
|
-
function runBackgroundShortcut(input) {
|
|
42999
|
-
const status = backgroundStatus();
|
|
43030
|
+
function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
43000
43031
|
if (!status.installed)
|
|
43001
43032
|
throw new Error(
|
|
43002
43033
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
@@ -43098,6 +43129,11 @@ function assertPreserved(before, after, options = {}) {
|
|
|
43098
43129
|
}
|
|
43099
43130
|
}
|
|
43100
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
|
+
}
|
|
43101
43137
|
function mutateBackground(request, operation, data, verify, deps) {
|
|
43102
43138
|
if (request.scopeText.length < 12 || request.scopeText.length > 500 || /[\r\n\0]/u.test(request.scopeText))
|
|
43103
43139
|
throw new Error("Use a distinctive existing single-line scope of 12\u2013500 characters");
|
|
@@ -43117,9 +43153,7 @@ function mutateBackground(request, operation, data, verify, deps) {
|
|
|
43117
43153
|
deps.run({ ...data, operation, title: before.title, scopeText: request.scopeText });
|
|
43118
43154
|
} catch (error2) {
|
|
43119
43155
|
transportUncertain = true;
|
|
43120
|
-
|
|
43121
|
-
const named = detail?.shortcut ? `the "${detail.shortcut}" Shortcut` : "the background Shortcut";
|
|
43122
|
-
transportMessage = detail?.code === "ETIMEDOUT" ? `Shortcuts timed out waiting for ${named}; check for an interactive parameter or permission request` : `${named} failed: ${String(detail?.stderr || detail?.message || "no output").trim().slice(0, 400)}`;
|
|
43156
|
+
transportMessage = describeTransportFailure(error2);
|
|
43123
43157
|
}
|
|
43124
43158
|
const after = deps.read(request.id);
|
|
43125
43159
|
try {
|
|
@@ -43207,6 +43241,109 @@ function setNativeTag(manager, request) {
|
|
|
43207
43241
|
deps
|
|
43208
43242
|
);
|
|
43209
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
|
+
}
|
|
43210
43347
|
|
|
43211
43348
|
// src/tools/doctor.ts
|
|
43212
43349
|
function runDoctor(manager) {
|
|
@@ -43239,15 +43376,17 @@ function runDoctor(manager) {
|
|
|
43239
43376
|
status: fda ? "ok" : "warn",
|
|
43240
43377
|
detail: fda ? "granted \u2014 the Notes database is readable (checklist state, note metadata, note links, sync detail)" : `not granted \u2014 get-checklist-state, get-note-metadata, and the checklist annotations in get-note-markdown won't work; get-note-link fails on macOS 26+ (macOS 12-15 falls back to AppleScript); get-sync-status still answers but cannot see pending uploads. Everything else is pure AppleScript and is unaffected. In System Settings > Privacy & Security > Full Disk Access, grant access to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it and re-run doctor. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`
|
|
43241
43378
|
});
|
|
43379
|
+
const consentReminder = "After install or upgrade, run each bridge Shortcut once in the foreground in Shortcuts.app and choose Always Allow: a background run cannot display a first-run consent prompt, so an unanswered one stalls that bridge's native writes until they time out while this check stays ok";
|
|
43242
43380
|
try {
|
|
43243
43381
|
const bridgeStatuses = [NATIVE_TAGS_SHORTCUT, BACKGROUND_SHORTCUT].map(
|
|
43244
43382
|
(name) => nativeTagsStatus(name)
|
|
43245
43383
|
);
|
|
43246
43384
|
const missing = bridgeStatuses.filter((status) => !status.installed);
|
|
43385
|
+
const markdown = markdownBridgeDetail();
|
|
43247
43386
|
checks.push({
|
|
43248
43387
|
name: "Native write Shortcuts",
|
|
43249
43388
|
status: missing.length ? "warn" : "ok",
|
|
43250
|
-
detail: missing.length ? `missing: ${missing.map((status) => status.shortcut).join(", ")}. Run apple-notes-mcp setup and approve Add Shortcut in macOS` :
|
|
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}`
|
|
43251
43390
|
});
|
|
43252
43391
|
} catch (error2) {
|
|
43253
43392
|
checks.push({
|
|
@@ -43260,6 +43399,14 @@ function runDoctor(manager) {
|
|
|
43260
43399
|
const healthy = !checks.some((c) => c.status === "fail");
|
|
43261
43400
|
return { healthy, checks };
|
|
43262
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
|
+
}
|
|
43263
43410
|
function checkNodeRuntimeSignature() {
|
|
43264
43411
|
const name = "Node runtime signature";
|
|
43265
43412
|
try {
|
|
@@ -43882,7 +44029,8 @@ var VERIFIED_BACKGROUND = /* @__PURE__ */ new Set([
|
|
|
43882
44029
|
"insert-note-link",
|
|
43883
44030
|
"set-note-pinned",
|
|
43884
44031
|
"remove-native-tags",
|
|
43885
|
-
"replace-native-tag"
|
|
44032
|
+
"replace-native-tag",
|
|
44033
|
+
"create-note-markdown"
|
|
43886
44034
|
]);
|
|
43887
44035
|
var LIVE_VALIDATION_BLOCKERS = {};
|
|
43888
44036
|
var signingRefusal = "Installed Shortcuts refuses to sign this Notes action (unsupported features); no background fallback is enabled";
|
|
@@ -43970,16 +44118,24 @@ function registerNativeOperations(server2, manager) {
|
|
|
43970
44118
|
"set-note-pinned",
|
|
43971
44119
|
"remove-native-tags",
|
|
43972
44120
|
"replace-native-tag",
|
|
43973
|
-
"insert-note-link"
|
|
44121
|
+
"insert-note-link",
|
|
44122
|
+
"create-note-markdown"
|
|
43974
44123
|
];
|
|
43975
44124
|
let tagBridgeInstalled = false;
|
|
43976
44125
|
try {
|
|
43977
44126
|
tagBridgeInstalled = nativeTagBridgeStatus().installed;
|
|
43978
44127
|
} catch {
|
|
43979
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;
|
|
43980
44135
|
return {
|
|
43981
44136
|
bridge,
|
|
43982
44137
|
nativeTagBridgeInstalled: tagBridgeInstalled,
|
|
44138
|
+
markdownNoteBridgeInstalled: markdownBridgeInstalled,
|
|
43983
44139
|
mode: "background-only",
|
|
43984
44140
|
operations: Object.fromEntries(
|
|
43985
44141
|
native.map((name) => [
|
|
@@ -43987,8 +44143,8 @@ function registerNativeOperations(server2, manager) {
|
|
|
43987
44143
|
{
|
|
43988
44144
|
implemented: true,
|
|
43989
44145
|
verified: VERIFIED_BACKGROUND.has(name),
|
|
43990
|
-
available: VERIFIED_BACKGROUND.has(name) && (
|
|
43991
|
-
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
|
|
43992
44148
|
}
|
|
43993
44149
|
])
|
|
43994
44150
|
),
|
|
@@ -44205,13 +44361,21 @@ function registerNativeOperations(server2, manager) {
|
|
|
44205
44361
|
// src/setupShortcuts.ts
|
|
44206
44362
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
44207
44363
|
import { existsSync as existsSync7 } from "node:fs";
|
|
44364
|
+
import { release } from "node:os";
|
|
44208
44365
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
44209
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;
|
|
44210
44369
|
var shortcutFiles = [
|
|
44211
44370
|
{ name: NATIVE_TAGS_SHORTCUT, file: "Apple Notes MCP - Native Tags.shortcut" },
|
|
44212
44371
|
{
|
|
44213
44372
|
name: BACKGROUND_SHORTCUT,
|
|
44214
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
|
|
44215
44379
|
}
|
|
44216
44380
|
];
|
|
44217
44381
|
function setupShortcuts(checkOnly, dependencies = {}) {
|
|
@@ -44222,7 +44386,9 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
44222
44386
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
44223
44387
|
});
|
|
44224
44388
|
const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
44225
|
-
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 }) => {
|
|
44226
44392
|
const path4 = resolve2(baseDirectory, file);
|
|
44227
44393
|
let installed = false;
|
|
44228
44394
|
let identifier;
|
|
@@ -44235,32 +44401,49 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
44235
44401
|
error2 = cause instanceof Error ? cause.message : String(cause);
|
|
44236
44402
|
}
|
|
44237
44403
|
let opened = false;
|
|
44404
|
+
let skipped;
|
|
44238
44405
|
if (!installed && !checkOnly) {
|
|
44239
|
-
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}`;
|
|
44240
44409
|
else {
|
|
44241
44410
|
const result = open(path4);
|
|
44242
44411
|
opened = result.ok;
|
|
44243
44412
|
if (!result.ok) error2 = result.error || `Could not open ${file}`;
|
|
44244
44413
|
}
|
|
44245
44414
|
}
|
|
44246
|
-
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
|
+
};
|
|
44247
44425
|
});
|
|
44248
|
-
return { ready: items.every((item) => item.installed), checkOnly, items };
|
|
44426
|
+
return { ready: items.every((item) => item.optional || item.installed), checkOnly, items };
|
|
44249
44427
|
}
|
|
44250
44428
|
function formatShortcutSetup(report) {
|
|
44251
44429
|
const lines = ["Apple Notes MCP Shortcut setup", ""];
|
|
44252
44430
|
for (const item of report.items) {
|
|
44253
|
-
|
|
44254
|
-
|
|
44255
|
-
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}`);
|
|
44256
44436
|
}
|
|
44257
44437
|
lines.push("");
|
|
44258
|
-
if (report.ready) lines.push("Both Shortcut bridges are installed.");
|
|
44438
|
+
if (report.ready) lines.push("Both required Shortcut bridges are installed.");
|
|
44259
44439
|
else if (report.checkOnly) lines.push("Run `apple-notes-mcp setup` to open missing workflows.");
|
|
44260
44440
|
else
|
|
44261
44441
|
lines.push(
|
|
44262
44442
|
"After approving the macOS dialogs, run `apple-notes-mcp setup --check` or the MCP doctor tool."
|
|
44263
44443
|
);
|
|
44444
|
+
lines.push(
|
|
44445
|
+
"After install or upgrade, run each bridge once in the foreground in Shortcuts.app and choose Always Allow; a background run cannot display a first-run consent prompt and stalls until it times out."
|
|
44446
|
+
);
|
|
44264
44447
|
return lines.join("\n");
|
|
44265
44448
|
}
|
|
44266
44449
|
|
|
@@ -44368,9 +44551,11 @@ registerTool(
|
|
|
44368
44551
|
content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).describe(
|
|
44369
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.'
|
|
44370
44553
|
),
|
|
44371
|
-
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
|
+
),
|
|
44372
44557
|
tags: external_exports.array(external_exports.string().max(MAX.TAG)).max(MAX.TAGS).optional().describe(
|
|
44373
|
-
"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."
|
|
44374
44559
|
),
|
|
44375
44560
|
folder: external_exports.string().max(MAX.FOLDER).optional().describe(
|
|
44376
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."
|
|
@@ -44390,6 +44575,19 @@ registerTool(
|
|
|
44390
44575
|
}
|
|
44391
44576
|
},
|
|
44392
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
|
+
}
|
|
44393
44591
|
const note = notesManager.createNote(title, content, tags, folder, account, format);
|
|
44394
44592
|
if (!note) {
|
|
44395
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
|
@@ -15,6 +15,13 @@ Open the signed file and confirm **Add Shortcut** once. macOS may also request
|
|
|
15
15
|
Notes access on first execution. The `shortcuts` CLI can run an installed
|
|
16
16
|
workflow but cannot silently import one.
|
|
17
17
|
|
|
18
|
+
After install and after every upgrade, run each bridge once in the foreground in
|
|
19
|
+
Shortcuts.app and choose **Always Allow** when it asks for permission. The
|
|
20
|
+
server runs bridges in the background, where Shortcuts cannot display a
|
|
21
|
+
first-run consent prompt, so an unanswered one stalls that bridge's native
|
|
22
|
+
writes until they time out. Quitting or relaunching Shortcuts.app or Notes.app
|
|
23
|
+
does not clear it; one foreground run per bridge does.
|
|
24
|
+
|
|
18
25
|
The bridge receives a private temporary JSON file with a title, a distinctive
|
|
19
26
|
existing scope phrase, and normalized tags. Both the server and workflow refuse
|
|
20
27
|
ambiguous note selection. Server readback, rather than CLI output, establishes
|
|
@@ -38,6 +45,39 @@ shortcuts sign --mode anyone \
|
|
|
38
45
|
--output "shortcuts/Apple Notes MCP - Background Operations v5.shortcut"
|
|
39
46
|
```
|
|
40
47
|
|
|
41
|
-
Open the signed file and confirm **Add Shortcut** once
|
|
48
|
+
Open the signed file and confirm **Add Shortcut** once, then run it once in the
|
|
49
|
+
foreground in Shortcuts.app and choose **Always Allow** (see above). Do not install obsolete
|
|
42
50
|
workflow versions with the same display name; the server resolves one exact
|
|
43
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
|
+
```
|