apple-notes-mcp 2.8.23 → 2.8.25
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 +57 -0
- package/build/index.js +379 -63
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,6 +201,35 @@ for the investigation and verification behind each:
|
|
|
201
201
|
|
|
202
202
|
This section documents all available tools. AI agents should use these tool names and parameters exactly as specified.
|
|
203
203
|
|
|
204
|
+
### Error results
|
|
205
|
+
|
|
206
|
+
A failed call returns `isError: true` with the same human-readable text as
|
|
207
|
+
before, plus `structuredContent` carrying a stable machine-readable `code`.
|
|
208
|
+
Branch on `code`, not on the prose, which may be reworded.
|
|
209
|
+
|
|
210
|
+
| `code` | Meaning |
|
|
211
|
+
|--------|---------|
|
|
212
|
+
| `not_found` | The note, folder, account, attachment, or checklist does not exist |
|
|
213
|
+
| `ambiguous` | More than one item matched; use an exact id |
|
|
214
|
+
| `permission_denied` | macOS refused Automation access to Notes.app |
|
|
215
|
+
| `full_disk_access_missing` | The Notes database is not readable; grant Full Disk Access |
|
|
216
|
+
| `shortcut_not_installed` | A native-write bridge Shortcut is not installed exactly once |
|
|
217
|
+
| `timeout_indeterminate` | The operation timed out; for a write, the outcome is unknown |
|
|
218
|
+
| `verification_failed` | The write ran, but exact-ID readback did not confirm it |
|
|
219
|
+
| `revision_conflict` | The note changed since it was read |
|
|
220
|
+
| `validation_error` | The request was rejected before anything ran |
|
|
221
|
+
| `unsupported` | Not supported for this note or in this mode, such as a locked note |
|
|
222
|
+
| `notes_unavailable` | Notes.app is not running, busy, or not responding |
|
|
223
|
+
| `operation_failed` | The server could not classify the failure; read the text |
|
|
224
|
+
|
|
225
|
+
Two optional booleans describe a write's outcome when it is known.
|
|
226
|
+
`indeterminate: true` means the outcome is uncertain: read the target by exact
|
|
227
|
+
id before any retry, and never retry blindly. `committed: true` means the write
|
|
228
|
+
took effect even though its verification failed; `committed: false` means
|
|
229
|
+
nothing was written (for example a `revision_conflict`). An absent flag means
|
|
230
|
+
unknown. Errors the MCP SDK raises itself before a tool runs, such as an
|
|
231
|
+
argument that fails the input schema, carry no `structuredContent`.
|
|
232
|
+
|
|
204
233
|
### Note Operations
|
|
205
234
|
|
|
206
235
|
#### `create-note`
|
|
@@ -1221,6 +1250,10 @@ Run a full setup diagnostic: Notes.app reachability, the Automation permission,
|
|
|
1221
1250
|
|
|
1222
1251
|
**Returns:** A per-check report (`structuredContent` carries the raw `{healthy, checks[]}`). The Full Disk Access check tells you whether checklist-state features will work — see [Full Disk Access Setup](https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md).
|
|
1223
1252
|
|
|
1253
|
+
The report ends with the same feature matrix as [`get-capabilities`](#get-capabilities)
|
|
1254
|
+
(`structuredContent.runtimeOS` and `structuredContent.features`). It is
|
|
1255
|
+
informational: an unavailable optional feature never changes `healthy`.
|
|
1256
|
+
|
|
1224
1257
|
---
|
|
1225
1258
|
|
|
1226
1259
|
#### `get-notes-stats`
|
|
@@ -1273,6 +1306,30 @@ resolve the same note. Creating a note from Markdown is
|
|
|
1273
1306
|
Reports which background operations are implemented, live-verified, installed,
|
|
1274
1307
|
and currently available, with a specific reason for each unavailable operation.
|
|
1275
1308
|
|
|
1309
|
+
It also reports `runtimeOS` (`platform`, `macOSVersion` from `sw_vers`,
|
|
1310
|
+
`darwinRelease`) and a `features` matrix, one entry per feature group:
|
|
1311
|
+
`applescriptCore`, `fullDiskAccessReads`, `shortcutsBridges`,
|
|
1312
|
+
`backgroundOperationsBridge`, `nativeTagsBridge`, `markdownNoteBridge`, and the
|
|
1313
|
+
placeholders `checklistToggle`, `smartFolders`, `paragraphLinks`, and
|
|
1314
|
+
`audioTranscription`, which need a native helper this server does not ship.
|
|
1315
|
+
Each entry carries `available`, `osSupported`, `minimumMacOSVersion`,
|
|
1316
|
+
`requirements`, `missing`, `unverified`, `tools`, and a machine-readable
|
|
1317
|
+
`reason` that is `null` when available and otherwise the first that applies:
|
|
1318
|
+
|
|
1319
|
+
| `reason` | Meaning |
|
|
1320
|
+
|----------|---------|
|
|
1321
|
+
| `unsupported_platform` | Not running on macOS |
|
|
1322
|
+
| `not_implemented` | The server has no implementation for this feature yet |
|
|
1323
|
+
| `unknown_os_version` | The feature has a macOS floor and `sw_vers` could not be read |
|
|
1324
|
+
| `requires_macos_<major>` | macOS is older than `minimumMacOSVersion`, e.g. `requires_macos_26` |
|
|
1325
|
+
| `full_disk_access_missing` | The Notes database is not readable by this process |
|
|
1326
|
+
| `shortcuts_unavailable` | The `shortcuts` command could not be run |
|
|
1327
|
+
| `shortcut_not_installed` | A bridge Shortcut is missing or installed more than once |
|
|
1328
|
+
|
|
1329
|
+
The probe never opens Notes.app or runs a Shortcut, so the Automation
|
|
1330
|
+
permission for Notes.app appears under `unverified` rather than being guessed;
|
|
1331
|
+
`doctor` and `health-check` confirm it by contacting Notes.
|
|
1332
|
+
|
|
1276
1333
|
#### `append-native`
|
|
1277
1334
|
|
|
1278
1335
|
Appends bounded plaintext, semantic HTML, or Markdown while preserving existing
|
package/build/index.js
CHANGED
|
@@ -1105,11 +1105,11 @@ var require_util = __commonJS({
|
|
|
1105
1105
|
return false;
|
|
1106
1106
|
}
|
|
1107
1107
|
exports.schemaHasRules = schemaHasRules;
|
|
1108
|
-
function schemaHasRulesButRef(schema,
|
|
1108
|
+
function schemaHasRulesButRef(schema, RULES2) {
|
|
1109
1109
|
if (typeof schema == "boolean")
|
|
1110
1110
|
return !schema;
|
|
1111
1111
|
for (const key in schema)
|
|
1112
|
-
if (key !== "$ref" &&
|
|
1112
|
+
if (key !== "$ref" && RULES2.all[key])
|
|
1113
1113
|
return true;
|
|
1114
1114
|
return false;
|
|
1115
1115
|
}
|
|
@@ -2503,17 +2503,17 @@ var require_validate = __commonJS({
|
|
|
2503
2503
|
}
|
|
2504
2504
|
function schemaKeywords(it, types, typeErrors, errsCount) {
|
|
2505
2505
|
const { gen, schema, data, allErrors, opts, self } = it;
|
|
2506
|
-
const { RULES } = self;
|
|
2507
|
-
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema,
|
|
2508
|
-
gen.block(() => keywordCode(it, "$ref",
|
|
2506
|
+
const { RULES: RULES2 } = self;
|
|
2507
|
+
if (schema.$ref && (opts.ignoreKeywordsWithRef || !(0, util_1.schemaHasRulesButRef)(schema, RULES2))) {
|
|
2508
|
+
gen.block(() => keywordCode(it, "$ref", RULES2.all.$ref.definition));
|
|
2509
2509
|
return;
|
|
2510
2510
|
}
|
|
2511
2511
|
if (!opts.jtd)
|
|
2512
2512
|
checkStrictTypes(it, types);
|
|
2513
2513
|
gen.block(() => {
|
|
2514
|
-
for (const group of
|
|
2514
|
+
for (const group of RULES2.rules)
|
|
2515
2515
|
groupKeywords(group);
|
|
2516
|
-
groupKeywords(
|
|
2516
|
+
groupKeywords(RULES2.post);
|
|
2517
2517
|
});
|
|
2518
2518
|
function groupKeywords(group) {
|
|
2519
2519
|
if (!(0, applicability_1.shouldUseGroup)(schema, group))
|
|
@@ -4600,10 +4600,10 @@ var require_core = __commonJS({
|
|
|
4600
4600
|
}
|
|
4601
4601
|
// Remove keyword
|
|
4602
4602
|
removeKeyword(keyword) {
|
|
4603
|
-
const { RULES } = this;
|
|
4604
|
-
delete
|
|
4605
|
-
delete
|
|
4606
|
-
for (const group of
|
|
4603
|
+
const { RULES: RULES2 } = this;
|
|
4604
|
+
delete RULES2.keywords[keyword];
|
|
4605
|
+
delete RULES2.all[keyword];
|
|
4606
|
+
for (const group of RULES2.rules) {
|
|
4607
4607
|
const i = group.rules.findIndex((rule) => rule.keyword === keyword);
|
|
4608
4608
|
if (i >= 0)
|
|
4609
4609
|
group.rules.splice(i, 1);
|
|
@@ -4771,9 +4771,9 @@ var require_core = __commonJS({
|
|
|
4771
4771
|
}
|
|
4772
4772
|
var KEYWORD_NAME = /^[a-z_$][a-z0-9_$:-]*$/i;
|
|
4773
4773
|
function checkKeyword(keyword, def) {
|
|
4774
|
-
const { RULES } = this;
|
|
4774
|
+
const { RULES: RULES2 } = this;
|
|
4775
4775
|
(0, util_1.eachItem)(keyword, (kwd) => {
|
|
4776
|
-
if (
|
|
4776
|
+
if (RULES2.keywords[kwd])
|
|
4777
4777
|
throw new Error(`Keyword ${kwd} is already defined`);
|
|
4778
4778
|
if (!KEYWORD_NAME.test(kwd))
|
|
4779
4779
|
throw new Error(`Keyword ${kwd} has invalid name`);
|
|
@@ -4789,13 +4789,13 @@ var require_core = __commonJS({
|
|
|
4789
4789
|
const post = definition === null || definition === void 0 ? void 0 : definition.post;
|
|
4790
4790
|
if (dataType && post)
|
|
4791
4791
|
throw new Error('keyword with "post" flag cannot have "type"');
|
|
4792
|
-
const { RULES } = this;
|
|
4793
|
-
let ruleGroup = post ?
|
|
4792
|
+
const { RULES: RULES2 } = this;
|
|
4793
|
+
let ruleGroup = post ? RULES2.post : RULES2.rules.find(({ type: t }) => t === dataType);
|
|
4794
4794
|
if (!ruleGroup) {
|
|
4795
4795
|
ruleGroup = { type: dataType, rules: [] };
|
|
4796
|
-
|
|
4796
|
+
RULES2.rules.push(ruleGroup);
|
|
4797
4797
|
}
|
|
4798
|
-
|
|
4798
|
+
RULES2.keywords[keyword] = true;
|
|
4799
4799
|
if (!definition)
|
|
4800
4800
|
return;
|
|
4801
4801
|
const rule = {
|
|
@@ -4810,7 +4810,7 @@ var require_core = __commonJS({
|
|
|
4810
4810
|
addBeforeRule.call(this, ruleGroup, rule, definition.before);
|
|
4811
4811
|
else
|
|
4812
4812
|
ruleGroup.rules.push(rule);
|
|
4813
|
-
|
|
4813
|
+
RULES2.all[keyword] = rule;
|
|
4814
4814
|
(_a = definition.implements) === null || _a === void 0 ? void 0 : _a.forEach((kwd) => this.addKeyword(kwd));
|
|
4815
4815
|
}
|
|
4816
4816
|
function addBeforeRule(ruleGroup, rule, before) {
|
|
@@ -43598,13 +43598,19 @@ function addNativeTags(request, deps) {
|
|
|
43598
43598
|
} : {}
|
|
43599
43599
|
};
|
|
43600
43600
|
}
|
|
43601
|
-
function
|
|
43602
|
-
|
|
43601
|
+
function listInstalledShortcuts() {
|
|
43602
|
+
return execFileSync7("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
|
|
43603
43603
|
encoding: "utf8",
|
|
43604
43604
|
timeout: 15e3,
|
|
43605
43605
|
maxBuffer: 1024 * 1024,
|
|
43606
43606
|
stdio: ["ignore", "pipe", "pipe"]
|
|
43607
43607
|
}).split(/\r?\n/u);
|
|
43608
|
+
}
|
|
43609
|
+
var nativeTagsShortcutName = () => process.env.APPLE_NOTES_MCP_TAGS_SHORTCUT || NATIVE_TAGS_SHORTCUT;
|
|
43610
|
+
function nativeTagsStatus(shortcut = nativeTagsShortcutName()) {
|
|
43611
|
+
return resolveShortcut(listInstalledShortcuts(), shortcut);
|
|
43612
|
+
}
|
|
43613
|
+
function resolveShortcut(lines, shortcut) {
|
|
43608
43614
|
const matches = lines.flatMap((line) => {
|
|
43609
43615
|
const match = /^(.*) \(([0-9A-Fa-f-]{36})\)$/.exec(line);
|
|
43610
43616
|
return match && (match[1] === shortcut || match[2].toLowerCase() === shortcut.toLowerCase()) ? [{ name: match[1], identifier: match[2] }] : [];
|
|
@@ -43721,10 +43727,12 @@ function comparableVisibleText(html) {
|
|
|
43721
43727
|
|
|
43722
43728
|
// src/services/backgroundNotes.ts
|
|
43723
43729
|
var BACKGROUND_SHORTCUT = "Apple Notes MCP - Background Operations v5";
|
|
43724
|
-
var
|
|
43730
|
+
var backgroundShortcutName = () => process.env.APPLE_NOTES_MCP_BACKGROUND_SHORTCUT || BACKGROUND_SHORTCUT;
|
|
43731
|
+
var backgroundStatus = () => nativeTagsStatus(backgroundShortcutName());
|
|
43725
43732
|
var nativeTagBridgeStatus = () => nativeTagsStatus();
|
|
43726
43733
|
var MARKDOWN_NOTE_SHORTCUT = "Apple Notes MCP - Create Markdown Note";
|
|
43727
|
-
var
|
|
43734
|
+
var markdownShortcutName = () => process.env.APPLE_NOTES_MCP_MARKDOWN_SHORTCUT || MARKDOWN_NOTE_SHORTCUT;
|
|
43735
|
+
var markdownNoteStatus = () => nativeTagsStatus(markdownShortcutName());
|
|
43728
43736
|
function readBackgroundSnapshot(manager, id2) {
|
|
43729
43737
|
if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id2))
|
|
43730
43738
|
throw new Error("Exact note ID required");
|
|
@@ -44145,8 +44153,261 @@ ${request.content}`
|
|
|
44145
44153
|
}
|
|
44146
44154
|
}
|
|
44147
44155
|
|
|
44156
|
+
// src/services/capabilityMatrix.ts
|
|
44157
|
+
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
44158
|
+
import { release } from "node:os";
|
|
44159
|
+
var MACOS_SHORTCUTS_CLI = "12.0";
|
|
44160
|
+
var MACOS_MARKDOWN_IMPORT = "26.0";
|
|
44161
|
+
var FEATURES = [
|
|
44162
|
+
{
|
|
44163
|
+
name: "applescriptCore",
|
|
44164
|
+
description: "Create, read, search, update, move, and delete notes and folders through Notes.app's AppleScript interface",
|
|
44165
|
+
tools: [
|
|
44166
|
+
"create-note",
|
|
44167
|
+
"search-notes",
|
|
44168
|
+
"get-note-content",
|
|
44169
|
+
"update-note",
|
|
44170
|
+
"append-to-note",
|
|
44171
|
+
"insert-link",
|
|
44172
|
+
"delete-note",
|
|
44173
|
+
"move-note",
|
|
44174
|
+
"list-notes",
|
|
44175
|
+
"list-folders",
|
|
44176
|
+
"create-folder",
|
|
44177
|
+
"list-accounts"
|
|
44178
|
+
],
|
|
44179
|
+
minimumMacOSVersion: null,
|
|
44180
|
+
requirements: [{ kind: "notes_automation" }]
|
|
44181
|
+
},
|
|
44182
|
+
{
|
|
44183
|
+
name: "fullDiskAccessReads",
|
|
44184
|
+
description: "Read-only reads of the Notes database: query-notes, checklist state, note metadata, note links, native objects, native tags, and sync detail",
|
|
44185
|
+
tools: [
|
|
44186
|
+
"query-notes",
|
|
44187
|
+
"get-checklist-state",
|
|
44188
|
+
"get-note-metadata",
|
|
44189
|
+
"get-note-link",
|
|
44190
|
+
"get-native-objects",
|
|
44191
|
+
"list-native-tags",
|
|
44192
|
+
"get-note-markdown (checklist annotations)",
|
|
44193
|
+
"get-sync-status (pending uploads)"
|
|
44194
|
+
],
|
|
44195
|
+
minimumMacOSVersion: null,
|
|
44196
|
+
requirements: [{ kind: "full_disk_access" }]
|
|
44197
|
+
},
|
|
44198
|
+
{
|
|
44199
|
+
name: "shortcutsBridges",
|
|
44200
|
+
description: "Run the optional native-write Shortcut bridges in the background",
|
|
44201
|
+
tools: [],
|
|
44202
|
+
minimumMacOSVersion: MACOS_SHORTCUTS_CLI,
|
|
44203
|
+
requirements: [{ kind: "shortcuts_cli" }]
|
|
44204
|
+
},
|
|
44205
|
+
{
|
|
44206
|
+
name: "backgroundOperationsBridge",
|
|
44207
|
+
description: "Native background edits through the Background Operations bridge, verified by exact-ID database readback",
|
|
44208
|
+
tools: [
|
|
44209
|
+
"append-native",
|
|
44210
|
+
"create-checklist-item",
|
|
44211
|
+
"create-checklist-items",
|
|
44212
|
+
"create-table",
|
|
44213
|
+
"insert-note-link",
|
|
44214
|
+
"set-note-pinned",
|
|
44215
|
+
"remove-native-tags"
|
|
44216
|
+
],
|
|
44217
|
+
minimumMacOSVersion: MACOS_SHORTCUTS_CLI,
|
|
44218
|
+
requirements: [
|
|
44219
|
+
{ kind: "notes_automation" },
|
|
44220
|
+
{ kind: "full_disk_access" },
|
|
44221
|
+
{ kind: "shortcuts_cli" },
|
|
44222
|
+
{ kind: "shortcut", name: backgroundShortcutName }
|
|
44223
|
+
]
|
|
44224
|
+
},
|
|
44225
|
+
{
|
|
44226
|
+
name: "nativeTagsBridge",
|
|
44227
|
+
description: "Add real Notes tags through the Native Tags bridge",
|
|
44228
|
+
tools: ["add-native-tags", "replace-native-tag"],
|
|
44229
|
+
minimumMacOSVersion: MACOS_SHORTCUTS_CLI,
|
|
44230
|
+
requirements: [
|
|
44231
|
+
{ kind: "notes_automation" },
|
|
44232
|
+
{ kind: "full_disk_access" },
|
|
44233
|
+
{ kind: "shortcuts_cli" },
|
|
44234
|
+
{ kind: "shortcut", name: nativeTagsShortcutName }
|
|
44235
|
+
]
|
|
44236
|
+
},
|
|
44237
|
+
{
|
|
44238
|
+
name: "markdownNoteBridge",
|
|
44239
|
+
description: "Create a note from Markdown with real Title, Heading, and Subheading styles through the Create Markdown Note bridge",
|
|
44240
|
+
tools: ['create-note (format: "markdown")'],
|
|
44241
|
+
minimumMacOSVersion: MACOS_MARKDOWN_IMPORT,
|
|
44242
|
+
requirements: [
|
|
44243
|
+
{ kind: "notes_automation" },
|
|
44244
|
+
{ kind: "full_disk_access" },
|
|
44245
|
+
{ kind: "shortcuts_cli" },
|
|
44246
|
+
{ kind: "shortcut", name: markdownShortcutName }
|
|
44247
|
+
]
|
|
44248
|
+
},
|
|
44249
|
+
// Placeholders for features that need a native helper this server does not
|
|
44250
|
+
// ship. They always report `not_implemented` so clients can detect them.
|
|
44251
|
+
{
|
|
44252
|
+
name: "checklistToggle",
|
|
44253
|
+
description: "Check or uncheck an existing checklist item in place",
|
|
44254
|
+
tools: [],
|
|
44255
|
+
minimumMacOSVersion: null,
|
|
44256
|
+
requirements: [{ kind: "native_helper" }]
|
|
44257
|
+
},
|
|
44258
|
+
{
|
|
44259
|
+
name: "smartFolders",
|
|
44260
|
+
description: "Create or edit Smart Folders and their tag rules",
|
|
44261
|
+
tools: [],
|
|
44262
|
+
minimumMacOSVersion: null,
|
|
44263
|
+
requirements: [{ kind: "native_helper" }]
|
|
44264
|
+
},
|
|
44265
|
+
{
|
|
44266
|
+
name: "paragraphLinks",
|
|
44267
|
+
description: "Link to a specific paragraph or heading inside a note",
|
|
44268
|
+
tools: [],
|
|
44269
|
+
minimumMacOSVersion: null,
|
|
44270
|
+
requirements: [{ kind: "native_helper" }]
|
|
44271
|
+
},
|
|
44272
|
+
{
|
|
44273
|
+
name: "audioTranscription",
|
|
44274
|
+
description: "Transcribe audio recordings attached to a note",
|
|
44275
|
+
tools: [],
|
|
44276
|
+
minimumMacOSVersion: null,
|
|
44277
|
+
requirements: [{ kind: "native_helper" }]
|
|
44278
|
+
}
|
|
44279
|
+
];
|
|
44280
|
+
function requirementLabel(requirement) {
|
|
44281
|
+
return requirement.kind === "shortcut" ? `shortcut:${requirement.name()}` : requirement.kind;
|
|
44282
|
+
}
|
|
44283
|
+
function compareVersions(a, b) {
|
|
44284
|
+
const pa = a.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
44285
|
+
const pb = b.split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
44286
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
44287
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
44288
|
+
if (diff !== 0) return diff;
|
|
44289
|
+
}
|
|
44290
|
+
return 0;
|
|
44291
|
+
}
|
|
44292
|
+
function evaluateFeature(feature, env) {
|
|
44293
|
+
const isMac = env.platform === "darwin";
|
|
44294
|
+
const floor = feature.minimumMacOSVersion;
|
|
44295
|
+
const versionKnown = env.macOSVersion !== null;
|
|
44296
|
+
const osSupported = isMac && (floor === null || versionKnown && compareVersions(env.macOSVersion, floor) >= 0);
|
|
44297
|
+
const missing = [];
|
|
44298
|
+
const unverified = [];
|
|
44299
|
+
const failures = /* @__PURE__ */ new Set();
|
|
44300
|
+
for (const requirement of feature.requirements) {
|
|
44301
|
+
const label = requirementLabel(requirement);
|
|
44302
|
+
switch (requirement.kind) {
|
|
44303
|
+
case "notes_automation":
|
|
44304
|
+
unverified.push(label);
|
|
44305
|
+
break;
|
|
44306
|
+
case "full_disk_access":
|
|
44307
|
+
if (!env.fullDiskAccess) {
|
|
44308
|
+
missing.push(label);
|
|
44309
|
+
failures.add("full_disk_access_missing");
|
|
44310
|
+
}
|
|
44311
|
+
break;
|
|
44312
|
+
case "shortcuts_cli":
|
|
44313
|
+
if (env.shortcutLines === null) {
|
|
44314
|
+
missing.push(label);
|
|
44315
|
+
failures.add("shortcuts_unavailable");
|
|
44316
|
+
}
|
|
44317
|
+
break;
|
|
44318
|
+
case "shortcut":
|
|
44319
|
+
if (env.shortcutLines === null) unverified.push(label);
|
|
44320
|
+
else if (!resolveShortcut(env.shortcutLines, requirement.name()).installed) {
|
|
44321
|
+
missing.push(label);
|
|
44322
|
+
failures.add("shortcut_not_installed");
|
|
44323
|
+
}
|
|
44324
|
+
break;
|
|
44325
|
+
case "native_helper":
|
|
44326
|
+
missing.push(label);
|
|
44327
|
+
failures.add("not_implemented");
|
|
44328
|
+
break;
|
|
44329
|
+
}
|
|
44330
|
+
}
|
|
44331
|
+
let reason = null;
|
|
44332
|
+
if (!isMac) reason = "unsupported_platform";
|
|
44333
|
+
else if (failures.has("not_implemented")) reason = "not_implemented";
|
|
44334
|
+
else if (floor !== null && !versionKnown) reason = "unknown_os_version";
|
|
44335
|
+
else if (!osSupported) reason = `requires_macos_${Number.parseInt(floor, 10)}`;
|
|
44336
|
+
else if (failures.has("full_disk_access_missing")) reason = "full_disk_access_missing";
|
|
44337
|
+
else if (failures.has("shortcuts_unavailable")) reason = "shortcuts_unavailable";
|
|
44338
|
+
else if (failures.has("shortcut_not_installed")) reason = "shortcut_not_installed";
|
|
44339
|
+
return {
|
|
44340
|
+
description: feature.description,
|
|
44341
|
+
tools: feature.tools,
|
|
44342
|
+
available: reason === null,
|
|
44343
|
+
osSupported,
|
|
44344
|
+
minimumMacOSVersion: floor,
|
|
44345
|
+
requirements: feature.requirements.map(requirementLabel),
|
|
44346
|
+
missing,
|
|
44347
|
+
unverified,
|
|
44348
|
+
reason
|
|
44349
|
+
};
|
|
44350
|
+
}
|
|
44351
|
+
function evaluateFeatures(env, features = FEATURES) {
|
|
44352
|
+
return {
|
|
44353
|
+
runtimeOS: {
|
|
44354
|
+
platform: env.platform,
|
|
44355
|
+
macOSVersion: env.macOSVersion,
|
|
44356
|
+
darwinRelease: env.darwinRelease
|
|
44357
|
+
},
|
|
44358
|
+
features: Object.fromEntries(features.map((f) => [f.name, evaluateFeature(f, env)]))
|
|
44359
|
+
};
|
|
44360
|
+
}
|
|
44361
|
+
function readMacOSVersion() {
|
|
44362
|
+
if (process.platform !== "darwin") return null;
|
|
44363
|
+
try {
|
|
44364
|
+
const out = execFileSync9("/usr/bin/sw_vers", ["-productVersion"], {
|
|
44365
|
+
encoding: "utf8",
|
|
44366
|
+
timeout: 3e3,
|
|
44367
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
44368
|
+
}).trim();
|
|
44369
|
+
return /^\d+(\.\d+)*$/.test(out) ? out : null;
|
|
44370
|
+
} catch {
|
|
44371
|
+
return null;
|
|
44372
|
+
}
|
|
44373
|
+
}
|
|
44374
|
+
function probeCapabilityEnvironment() {
|
|
44375
|
+
const platform = process.platform;
|
|
44376
|
+
const isMac = platform === "darwin";
|
|
44377
|
+
let shortcutLines = null;
|
|
44378
|
+
if (isMac) {
|
|
44379
|
+
try {
|
|
44380
|
+
shortcutLines = listInstalledShortcuts();
|
|
44381
|
+
} catch {
|
|
44382
|
+
shortcutLines = null;
|
|
44383
|
+
}
|
|
44384
|
+
}
|
|
44385
|
+
return {
|
|
44386
|
+
platform,
|
|
44387
|
+
macOSVersion: readMacOSVersion(),
|
|
44388
|
+
darwinRelease: release(),
|
|
44389
|
+
fullDiskAccess: isMac && hasFullDiskAccess(),
|
|
44390
|
+
shortcutLines
|
|
44391
|
+
};
|
|
44392
|
+
}
|
|
44393
|
+
function getCapabilityMatrix(probe = probeCapabilityEnvironment) {
|
|
44394
|
+
return evaluateFeatures(probe());
|
|
44395
|
+
}
|
|
44396
|
+
function formatCapabilityMatrix(matrix) {
|
|
44397
|
+
const os5 = matrix.runtimeOS;
|
|
44398
|
+
const lines = [
|
|
44399
|
+
`Feature matrix (macOS ${os5.macOSVersion ?? "unknown"}, Darwin ${os5.darwinRelease}):`
|
|
44400
|
+
];
|
|
44401
|
+
for (const [name, status] of Object.entries(matrix.features)) {
|
|
44402
|
+
lines.push(
|
|
44403
|
+
status.available ? ` \u2713 ${name}: available${status.unverified.length ? ` (unverified: ${status.unverified.join(", ")})` : ""}` : ` \u2717 ${name}: ${status.reason}${status.missing.length ? ` (missing: ${status.missing.join(", ")})` : ""}`
|
|
44404
|
+
);
|
|
44405
|
+
}
|
|
44406
|
+
return lines.join("\n");
|
|
44407
|
+
}
|
|
44408
|
+
|
|
44148
44409
|
// src/tools/doctor.ts
|
|
44149
|
-
function runDoctor(manager) {
|
|
44410
|
+
function runDoctor(manager, capabilityMatrix = getCapabilityMatrix) {
|
|
44150
44411
|
const checks = [];
|
|
44151
44412
|
const hc = manager.healthCheck();
|
|
44152
44413
|
for (const c of hc.checks) {
|
|
@@ -44197,7 +44458,13 @@ function runDoctor(manager) {
|
|
|
44197
44458
|
}
|
|
44198
44459
|
checks.push(checkNodeRuntimeSignature());
|
|
44199
44460
|
const healthy = !checks.some((c) => c.status === "fail");
|
|
44200
|
-
|
|
44461
|
+
let matrix;
|
|
44462
|
+
try {
|
|
44463
|
+
matrix = capabilityMatrix();
|
|
44464
|
+
} catch {
|
|
44465
|
+
matrix = void 0;
|
|
44466
|
+
}
|
|
44467
|
+
return matrix ? { healthy, checks, runtimeOS: matrix.runtimeOS, features: matrix.features } : { healthy, checks };
|
|
44201
44468
|
}
|
|
44202
44469
|
function markdownBridgeDetail() {
|
|
44203
44470
|
const purpose = 'needed only for create-note format: "markdown" on macOS 26+';
|
|
@@ -44241,6 +44508,8 @@ function formatDoctorReport(r) {
|
|
|
44241
44508
|
const icon = (s) => s === "ok" ? "\u2705" : s === "warn" ? "\u26A0\uFE0F " : "\u274C";
|
|
44242
44509
|
const lines = [`\u{1FA7A} apple-notes-mcp doctor \u2014 ${r.healthy ? "healthy" : "ISSUES FOUND"}`, ""];
|
|
44243
44510
|
for (const c of r.checks) lines.push(`${icon(c.status)} ${c.name}: ${c.detail}`);
|
|
44511
|
+
if (r.runtimeOS && r.features)
|
|
44512
|
+
lines.push("", formatCapabilityMatrix({ runtimeOS: r.runtimeOS, features: r.features }));
|
|
44244
44513
|
return lines.join("\n");
|
|
44245
44514
|
}
|
|
44246
44515
|
|
|
@@ -44458,6 +44727,77 @@ function withJsonSchema2020_12(transport2) {
|
|
|
44458
44727
|
return transport2;
|
|
44459
44728
|
}
|
|
44460
44729
|
|
|
44730
|
+
// src/utils/errorCodes.ts
|
|
44731
|
+
var CodedError = class extends Error {
|
|
44732
|
+
envelope;
|
|
44733
|
+
constructor(message, envelope) {
|
|
44734
|
+
super(message);
|
|
44735
|
+
this.name = "CodedError";
|
|
44736
|
+
this.envelope = envelope;
|
|
44737
|
+
}
|
|
44738
|
+
};
|
|
44739
|
+
var RULES = [
|
|
44740
|
+
{ code: "timeout_indeterminate", pattern: /timed out|\bETIMEDOUT\b/i },
|
|
44741
|
+
{
|
|
44742
|
+
code: "verification_failed",
|
|
44743
|
+
pattern: /\buncertain\b|may have (?:been|succeeded)|\baccepted (?:the|an) \w+, but|\bnot verified\b|Do not retry automatically|before any retry|read (?:the )?(?:exact )?(?:note|ID)\b[^.]*before retr/i
|
|
44744
|
+
},
|
|
44745
|
+
{
|
|
44746
|
+
code: "revision_conflict",
|
|
44747
|
+
pattern: /changed after it was read|revision changed|(?:pinned|note) state changed|changed during (?:read|preflight)|changed; read (?:it|the note) again|rich text do not match/i
|
|
44748
|
+
},
|
|
44749
|
+
{ code: "full_disk_access_missing", pattern: /Full Disk Access/i },
|
|
44750
|
+
{
|
|
44751
|
+
code: "shortcut_not_installed",
|
|
44752
|
+
pattern: /(?:Install|Import) the supplied|no such shortcut|Run apple-notes-mcp setup/i
|
|
44753
|
+
},
|
|
44754
|
+
{
|
|
44755
|
+
code: "not_found",
|
|
44756
|
+
pattern: /not found|does not exist|\bNo \w+ found\b|does not contain any checklist items|Can[’']t get (?:note|folder|account|attachment)|Cannot find note|Scope is absent/i
|
|
44757
|
+
},
|
|
44758
|
+
{ code: "ambiguous", pattern: /ambiguous|more than one|exactly once|Duplicate note IDs/i },
|
|
44759
|
+
{
|
|
44760
|
+
code: "notes_unavailable",
|
|
44761
|
+
pattern: /Notes\.app is (?:not responding|busy)|Lost connection to Notes|isn't running/i
|
|
44762
|
+
},
|
|
44763
|
+
{
|
|
44764
|
+
code: "unsupported",
|
|
44765
|
+
pattern: /password-protected|Locked notes|unsupported|not supported|is blocked|blocked because|has not passed live|refuses to sign|No (?:supported )?background|only one where|Real Notes link unavailable/i
|
|
44766
|
+
},
|
|
44767
|
+
{
|
|
44768
|
+
code: "validation_error",
|
|
44769
|
+
pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to write|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
|
|
44770
|
+
}
|
|
44771
|
+
];
|
|
44772
|
+
var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replacement started|No content was (?:replaced|appended)|Nothing was created/i;
|
|
44773
|
+
var WRITE_ACCEPTED = /\baccepted (?:the|an) \w+, but/i;
|
|
44774
|
+
function classifyError(message, cause) {
|
|
44775
|
+
if (cause instanceof CodedError) return { ...cause.envelope };
|
|
44776
|
+
const text = cause instanceof Error && cause.message && !message.includes(cause.message) ? `${message}
|
|
44777
|
+
${cause.message}` : message;
|
|
44778
|
+
const timeoutCause = typeof cause === "object" && cause !== null && cause.code === "ETIMEDOUT";
|
|
44779
|
+
let code = "operation_failed";
|
|
44780
|
+
if (timeoutCause) code = "timeout_indeterminate";
|
|
44781
|
+
else if (isPermissionDenied(text)) code = "permission_denied";
|
|
44782
|
+
else code = RULES.find((rule) => rule.pattern.test(text))?.code ?? "operation_failed";
|
|
44783
|
+
const envelope = { code };
|
|
44784
|
+
if (code === "timeout_indeterminate" || code === "verification_failed") {
|
|
44785
|
+
envelope.indeterminate = true;
|
|
44786
|
+
if (WRITE_ACCEPTED.test(text)) envelope.committed = true;
|
|
44787
|
+
} else if (code === "revision_conflict" || NOTHING_WRITTEN.test(text)) {
|
|
44788
|
+
envelope.committed = false;
|
|
44789
|
+
envelope.indeterminate = false;
|
|
44790
|
+
}
|
|
44791
|
+
return envelope;
|
|
44792
|
+
}
|
|
44793
|
+
function errorResult(message, cause) {
|
|
44794
|
+
return {
|
|
44795
|
+
content: [{ type: "text", text: message }],
|
|
44796
|
+
structuredContent: classifyError(message, cause),
|
|
44797
|
+
isError: true
|
|
44798
|
+
};
|
|
44799
|
+
}
|
|
44800
|
+
|
|
44461
44801
|
// src/utils/shutdown.ts
|
|
44462
44802
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = 2e3;
|
|
44463
44803
|
function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
|
|
@@ -44778,15 +45118,7 @@ function registerDirectOperations(server2, manager) {
|
|
|
44778
45118
|
structuredContent: result
|
|
44779
45119
|
};
|
|
44780
45120
|
} catch (error2) {
|
|
44781
|
-
return
|
|
44782
|
-
content: [
|
|
44783
|
-
{
|
|
44784
|
-
type: "text",
|
|
44785
|
-
text: error2 instanceof Error ? error2.message : String(error2)
|
|
44786
|
-
}
|
|
44787
|
-
],
|
|
44788
|
-
isError: true
|
|
44789
|
-
};
|
|
45121
|
+
return errorResult(error2 instanceof Error ? error2.message : String(error2), error2);
|
|
44790
45122
|
}
|
|
44791
45123
|
})
|
|
44792
45124
|
);
|
|
@@ -44893,15 +45225,7 @@ function registerNativeTagsBridge(server2, manager) {
|
|
|
44893
45225
|
structuredContent: result
|
|
44894
45226
|
};
|
|
44895
45227
|
} catch (error2) {
|
|
44896
|
-
return
|
|
44897
|
-
content: [
|
|
44898
|
-
{
|
|
44899
|
-
type: "text",
|
|
44900
|
-
text: error2 instanceof Error ? error2.message : String(error2)
|
|
44901
|
-
}
|
|
44902
|
-
],
|
|
44903
|
-
isError: true
|
|
44904
|
-
};
|
|
45228
|
+
return errorResult(error2 instanceof Error ? error2.message : String(error2), error2);
|
|
44905
45229
|
}
|
|
44906
45230
|
})
|
|
44907
45231
|
);
|
|
@@ -45088,22 +45412,14 @@ function registerNativeOperations(server2, manager) {
|
|
|
45088
45412
|
structuredContent: result
|
|
45089
45413
|
};
|
|
45090
45414
|
} catch (error2) {
|
|
45091
|
-
return
|
|
45092
|
-
content: [
|
|
45093
|
-
{
|
|
45094
|
-
type: "text",
|
|
45095
|
-
text: error2 instanceof Error ? error2.message : "Operation failed"
|
|
45096
|
-
}
|
|
45097
|
-
],
|
|
45098
|
-
isError: true
|
|
45099
|
-
};
|
|
45415
|
+
return errorResult(error2 instanceof Error ? error2.message : "Operation failed", error2);
|
|
45100
45416
|
}
|
|
45101
45417
|
})
|
|
45102
45418
|
);
|
|
45103
45419
|
}
|
|
45104
45420
|
tool(
|
|
45105
45421
|
"get-capabilities",
|
|
45106
|
-
"Use when: checking native background-edit support before calling a
|
|
45422
|
+
"Use when: checking native background-edit support or which feature groups this Mac supports before calling a tool.\nReturns: bridge installation, implementation, verification, availability, and specific limitations per operation; plus runtimeOS and an OS-version-aware features matrix (available, osSupported, minimumMacOSVersion, requirements, missing, unverified, machine reason code) per feature group.\nDo not use when: checking only the Native Tags bridge (native-tags-status).\nSafety: read-only; does not open Notes or run a mutation.",
|
|
45107
45423
|
{},
|
|
45108
45424
|
() => {
|
|
45109
45425
|
let bridge;
|
|
@@ -45153,7 +45469,8 @@ function registerNativeOperations(server2, manager) {
|
|
|
45153
45469
|
}
|
|
45154
45470
|
])
|
|
45155
45471
|
),
|
|
45156
|
-
unavailable: UNAVAILABLE
|
|
45472
|
+
unavailable: UNAVAILABLE,
|
|
45473
|
+
...getCapabilityMatrix()
|
|
45157
45474
|
};
|
|
45158
45475
|
},
|
|
45159
45476
|
true
|
|
@@ -45380,7 +45697,7 @@ function registerNativeOperations(server2, manager) {
|
|
|
45380
45697
|
// src/setupShortcuts.ts
|
|
45381
45698
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
45382
45699
|
import { existsSync as existsSync8 } from "node:fs";
|
|
45383
|
-
import { release } from "node:os";
|
|
45700
|
+
import { release as release2 } from "node:os";
|
|
45384
45701
|
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
45385
45702
|
import { fileURLToPath } from "node:url";
|
|
45386
45703
|
var OPTIONAL_BRIDGE_NOTE = "(optional \u2014 needed only for create-note format: markdown, macOS 26+)";
|
|
@@ -45405,7 +45722,7 @@ function setupShortcuts(checkOnly, dependencies = {}) {
|
|
|
45405
45722
|
return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
|
|
45406
45723
|
});
|
|
45407
45724
|
const baseDirectory = dependencies.baseDirectory || resolve2(dirname2(fileURLToPath(import.meta.url)), "../shortcuts");
|
|
45408
|
-
const osRelease = (dependencies.osRelease ||
|
|
45725
|
+
const osRelease = (dependencies.osRelease || release2)();
|
|
45409
45726
|
const darwinMajor = Number.parseInt(osRelease.split(".")[0], 10);
|
|
45410
45727
|
const items = shortcutFiles.map(({ name, file, optional: optional2 }) => {
|
|
45411
45728
|
const path5 = resolve2(baseDirectory, file);
|
|
@@ -45489,11 +45806,8 @@ function successResponse(message, structured) {
|
|
|
45489
45806
|
if (structured) res.structuredContent = structured;
|
|
45490
45807
|
return res;
|
|
45491
45808
|
}
|
|
45492
|
-
function errorResponse(message) {
|
|
45493
|
-
return
|
|
45494
|
-
content: [{ type: "text", text: message }],
|
|
45495
|
-
isError: true
|
|
45496
|
-
};
|
|
45809
|
+
function errorResponse(message, cause) {
|
|
45810
|
+
return errorResult(message, cause);
|
|
45497
45811
|
}
|
|
45498
45812
|
function withErrorHandling(handler, errorPrefix) {
|
|
45499
45813
|
return async (params) => {
|
|
@@ -45501,7 +45815,7 @@ function withErrorHandling(handler, errorPrefix) {
|
|
|
45501
45815
|
return handler(params);
|
|
45502
45816
|
} catch (error2) {
|
|
45503
45817
|
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
45504
|
-
return errorResponse(`${errorPrefix}: ${message}
|
|
45818
|
+
return errorResponse(`${errorPrefix}: ${message}`, error2);
|
|
45505
45819
|
}
|
|
45506
45820
|
};
|
|
45507
45821
|
}
|
|
@@ -46939,11 +47253,13 @@ ${fdaLine}`, {
|
|
|
46939
47253
|
registerTool(
|
|
46940
47254
|
"doctor",
|
|
46941
47255
|
{
|
|
46942
|
-
description: "Use when: diagnosing setup problems (Notes.app automation permission, account state, Full Disk Access) with actionable guidance.\nReturns: a detailed report plus structured fields.\nDo not use when: you just need a quick pass/fail (health-check).\nRead-only.",
|
|
47256
|
+
description: "Use when: diagnosing setup problems (Notes.app automation permission, account state, Full Disk Access) with actionable guidance.\nReturns: a detailed report plus structured fields, including runtimeOS and the same OS-version-aware features matrix as get-capabilities.\nDo not use when: you just need a quick pass/fail (health-check).\nRead-only.",
|
|
46943
47257
|
inputSchema: {},
|
|
46944
47258
|
outputSchema: {
|
|
46945
47259
|
healthy: external_exports.boolean().optional(),
|
|
46946
|
-
checks: external_exports.array(external_exports.object({}).passthrough()).optional()
|
|
47260
|
+
checks: external_exports.array(external_exports.object({}).passthrough()).optional(),
|
|
47261
|
+
runtimeOS: external_exports.object({}).passthrough().optional(),
|
|
47262
|
+
features: external_exports.record(external_exports.string(), external_exports.object({}).passthrough()).optional()
|
|
46947
47263
|
}
|
|
46948
47264
|
},
|
|
46949
47265
|
withErrorHandling(() => {
|
package/package.json
CHANGED