things-api 0.18.0 → 0.19.1
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 +18 -9
- package/deputy/AppIcon.icns +0 -0
- package/deputy/VERSION +1 -1
- package/deputy/helpers-Info.plist +4 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/CodeResources +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Info.plist +3 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/MacOS/things-reader +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Helpers/things-reader.app/Contents/_CodeSignature/CodeResources +15 -2
- package/deputy/prebuilt/Things API Helper.app/Contents/Info.plist +5 -1
- package/deputy/prebuilt/Things API Helper.app/Contents/MacOS/things-deputy +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/Resources/AppIcon.icns +0 -0
- package/deputy/prebuilt/Things API Helper.app/Contents/_CodeSignature/CodeResources +14 -2
- package/deputy/reader/Info.plist +2 -0
- package/deputy/reader/entitlements.plist +30 -0
- package/deputy/reader/main.swift +129 -40
- package/deputy/src/server.swift +25 -2
- package/deputy/src/tcc.swift +58 -0
- package/dist/audit/schema.d.ts +30 -1
- package/dist/audit/schema.js.map +1 -1
- package/dist/capability.d.ts +322 -0
- package/dist/capability.js +706 -0
- package/dist/capability.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +13 -1
- package/dist/cli/commands/doctor.js +141 -5
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/helpers.js +189 -51
- package/dist/cli/commands/helpers.js.map +1 -1
- package/dist/cli/commands/mcp.js +8 -0
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/op-result.js +6 -0
- package/dist/cli/commands/op-result.js.map +1 -1
- package/dist/cli/commands/reads.js +47 -1
- package/dist/cli/commands/reads.js.map +1 -1
- package/dist/cli/commands/repeat-flags.js +34 -2
- package/dist/cli/commands/repeat-flags.js.map +1 -1
- package/dist/cli/commands/setup.d.ts +9 -0
- package/dist/cli/commands/setup.js +76 -83
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/cli/commands/writes.d.ts +1 -1
- package/dist/cli/commands/writes.js +311 -142
- package/dist/cli/commands/writes.js.map +1 -1
- package/dist/cli/dry-run.d.ts +1 -1
- package/dist/cli/dry-run.js +1 -1
- package/dist/cli/glyphs.d.ts +23 -4
- package/dist/cli/glyphs.js +46 -19
- package/dist/cli/glyphs.js.map +1 -1
- package/dist/cli/help.d.ts +1 -1
- package/dist/cli/help.js +49 -27
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/interrupt.d.ts +54 -6
- package/dist/cli/interrupt.js +78 -12
- package/dist/cli/interrupt.js.map +1 -1
- package/dist/cli/main.js +8 -5
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/period.d.ts +3 -1
- package/dist/cli/period.js +1 -1
- package/dist/cli/period.js.map +1 -1
- package/dist/cli/read-driver.js +26 -1
- package/dist/cli/read-driver.js.map +1 -1
- package/dist/cli/render.d.ts +31 -4
- package/dist/cli/render.js +127 -15
- package/dist/cli/render.js.map +1 -1
- package/dist/client.d.ts +23 -3
- package/dist/client.js +36 -13
- package/dist/client.js.map +1 -1
- package/dist/contracts.d.ts +8 -4
- package/dist/contracts.js +1 -1
- package/dist/contracts.js.map +1 -1
- package/dist/deputy/install.d.ts +234 -6
- package/dist/deputy/install.js +843 -31
- package/dist/deputy/install.js.map +1 -1
- package/dist/deputy/notices.js +2 -2
- package/dist/deputy/notices.js.map +1 -1
- package/dist/deputy/protocol.d.ts +59 -5
- package/dist/deputy/protocol.js +48 -11
- package/dist/deputy/protocol.js.map +1 -1
- package/dist/deputy/routing.d.ts +37 -0
- package/dist/deputy/routing.js +181 -27
- package/dist/deputy/routing.js.map +1 -1
- package/dist/deputy/wake.d.ts +61 -0
- package/dist/deputy/wake.js +149 -0
- package/dist/deputy/wake.js.map +1 -0
- package/dist/diagnose.d.ts +19 -4
- package/dist/diagnose.js +64 -8
- package/dist/diagnose.js.map +1 -1
- package/dist/direct-setup.d.ts +104 -0
- package/dist/direct-setup.js +658 -0
- package/dist/direct-setup.js.map +1 -0
- package/dist/host-access.d.ts +75 -0
- package/dist/host-access.js +177 -0
- package/dist/host-access.js.map +1 -0
- package/dist/index.d.ts +20 -5
- package/dist/index.js +14 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts +27 -1
- package/dist/mcp/server.js +308 -109
- package/dist/mcp/server.js.map +1 -1
- package/dist/op-result.d.ts +9 -0
- package/dist/op-result.js +13 -1
- package/dist/op-result.js.map +1 -1
- package/dist/read/filter-contract.d.ts +1 -1
- package/dist/read/filter-contract.js +4 -0
- package/dist/read/filter-contract.js.map +1 -1
- package/dist/read/shape.js +4 -0
- package/dist/read/shape.js.map +1 -1
- package/dist/read/views.d.ts +29 -0
- package/dist/read/views.js +60 -0
- package/dist/read/views.js.map +1 -1
- package/dist/session-grant.d.ts +48 -0
- package/dist/session-grant.js +185 -0
- package/dist/session-grant.js.map +1 -0
- package/dist/surface-copy.d.ts +19 -3
- package/dist/surface-copy.js +19 -3
- package/dist/surface-copy.js.map +1 -1
- package/dist/wizard.d.ts +58 -0
- package/dist/wizard.js +174 -0
- package/dist/wizard.js.map +1 -0
- package/dist/write/availability.d.ts +1 -16
- package/dist/write/availability.js +10 -65
- package/dist/write/availability.js.map +1 -1
- package/dist/write/batch.d.ts +20 -0
- package/dist/write/batch.js +111 -21
- package/dist/write/batch.js.map +1 -1
- package/dist/write/capabilities.d.ts +9 -0
- package/dist/write/capabilities.js +2 -0
- package/dist/write/capabilities.js.map +1 -1
- package/dist/write/clear-reminder.js +1 -1
- package/dist/write/clear-reminder.js.map +1 -1
- package/dist/write/commands.d.ts +22 -7
- package/dist/write/commands.js +231 -254
- package/dist/write/commands.js.map +1 -1
- package/dist/write/failure-hints.d.ts +32 -7
- package/dist/write/failure-hints.js +9 -14
- package/dist/write/failure-hints.js.map +1 -1
- package/dist/write/guards.js +22 -4
- package/dist/write/guards.js.map +1 -1
- package/dist/write/lock.d.ts +16 -13
- package/dist/write/lock.js +55 -0
- package/dist/write/lock.js.map +1 -1
- package/dist/write/make-repeating-project.js +9 -13
- package/dist/write/make-repeating-project.js.map +1 -1
- package/dist/write/move.js +9 -12
- package/dist/write/move.js.map +1 -1
- package/dist/write/operations.d.ts +51 -29
- package/dist/write/operations.js +16 -0
- package/dist/write/operations.js.map +1 -1
- package/dist/write/opid.d.ts +72 -14
- package/dist/write/opid.js +152 -8
- package/dist/write/opid.js.map +1 -1
- package/dist/write/param-schema.d.ts +80 -0
- package/dist/write/param-schema.js +754 -0
- package/dist/write/param-schema.js.map +1 -0
- package/dist/write/pipeline.d.ts +110 -5
- package/dist/write/pipeline.js +288 -14
- package/dist/write/pipeline.js.map +1 -1
- package/dist/write/pre-state.d.ts +38 -16
- package/dist/write/pre-state.js +117 -22
- package/dist/write/pre-state.js.map +1 -1
- package/dist/write/promote-clone.js +658 -444
- package/dist/write/promote-clone.js.map +1 -1
- package/dist/write/reorder.js +25 -8
- package/dist/write/reorder.js.map +1 -1
- package/dist/write/repeat-collateral.d.ts +31 -0
- package/dist/write/repeat-collateral.js +153 -0
- package/dist/write/repeat-collateral.js.map +1 -0
- package/dist/write/repeat-rule.d.ts +28 -1
- package/dist/write/repeat-rule.js +54 -4
- package/dist/write/repeat-rule.js.map +1 -1
- package/dist/write/resolution-timestamps.d.ts +11 -0
- package/dist/write/resolution-timestamps.js +108 -40
- package/dist/write/resolution-timestamps.js.map +1 -1
- package/dist/write/reversibility.js +4 -0
- package/dist/write/reversibility.js.map +1 -1
- package/dist/write/template-mutation.d.ts +137 -0
- package/dist/write/template-mutation.js +598 -0
- package/dist/write/template-mutation.js.map +1 -0
- package/dist/write/undo.js +10 -20
- package/dist/write/undo.js.map +1 -1
- package/dist/write/update-fields.d.ts +151 -0
- package/dist/write/update-fields.js +427 -0
- package/dist/write/update-fields.js.map +1 -0
- package/dist/write/vectors/applescript.js +6 -10
- package/dist/write/vectors/applescript.js.map +1 -1
- package/dist/write/vectors/registry.d.ts +1 -1
- package/dist/write/vectors/registry.js +10 -2
- package/dist/write/vectors/registry.js.map +1 -1
- package/dist/write/vectors/shortcuts.js +2 -2
- package/dist/write/vectors/shortcuts.js.map +1 -1
- package/dist/write/vectors/simulator.js +15 -4
- package/dist/write/vectors/simulator.js.map +1 -1
- package/dist/write/vectors/types.d.ts +268 -1
- package/dist/write/vectors/ui-certification.js +63 -13
- package/dist/write/vectors/ui-certification.js.map +1 -1
- package/dist/write/vectors/ui-chord.d.ts +144 -0
- package/dist/write/vectors/ui-chord.js +428 -0
- package/dist/write/vectors/ui-chord.js.map +1 -0
- package/dist/write/vectors/ui-drag.d.ts +8 -4
- package/dist/write/vectors/ui-drag.js +2 -0
- package/dist/write/vectors/ui-drag.js.map +1 -1
- package/dist/write/vectors/ui-recipes.d.ts +70 -3
- package/dist/write/vectors/ui-recipes.js +482 -44
- package/dist/write/vectors/ui-recipes.js.map +1 -1
- package/dist/write/vectors/ui.d.ts +280 -10
- package/dist/write/vectors/ui.js +1068 -77
- package/dist/write/vectors/ui.js.map +1 -1
- package/dist/write/vectors/url-scheme.js +3 -0
- package/dist/write/vectors/url-scheme.js.map +1 -1
- package/dist/write/verify/delta.d.ts +62 -0
- package/dist/write/verify/delta.js +36 -2
- package/dist/write/verify/delta.js.map +1 -1
- package/dist/write/verify/poller.d.ts +4 -1
- package/dist/write/verify/poller.js +14 -0
- package/dist/write/verify/poller.js.map +1 -1
- package/package.json +2 -1
- package/schema/envelope.schema.json +6 -4
- package/scripts/build-helpers.sh +5 -0
- package/skills/things-cli/SKILL.md +5 -3
- package/skills/things-cli/references/contracts.md +1 -0
- package/skills/things-cli/references/data-model.md +3 -0
- package/skills/things-cli/references/errors.md +1 -1
- package/skills/things-cli/references/gui.md +1 -0
- package/skills/things-cli/references/repeating.md +6 -0
- package/skills/things-cli/references/tag-cleanup.md +64 -0
package/dist/mcp/server.js
CHANGED
|
@@ -16,7 +16,64 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
19
|
-
import { ALL_DESC, AREA_FILTER_DESC, AREA_LIMIT_DESC, AREA_PREVIEW_LIMIT, blockedCode, capabilitiesTable, DATE_FORMAT, DEFAULT_LIST_LIMIT, diagnose, FILTER_CONTRACT, FULL_DESC, hasTagPresence, isLooseRef, isValidTimeZone, LIMIT_DESC, looseShadowNotice, MCP_WHEN_LABELS, mutationWireData, noUuidMatch, omitEmpty, OMIT_EMPTY_NOTE, OP_ID_RE, opResult, OPERATION_KINDS, openThings, PKG_VERSION, PROJECT_LIMIT_DESC, PROJECT_PREVIEW_LIMIT, REF_FORMAT, REF_RULE_NOTE, ReferenceResolutionError, REMINDER_FORMAT, RESOLUTION_DATE_FORMAT, schemaWarnings, shapeReadPayload, withTodayBucketTotals, withAreaBucketTotals, withSectionTotals, withUpcomingBlockTotals, splitWhenSugar, tagFilterFields, tagFlagConflict, validateViewArgs, verifyFailedCode, WHEN_VALUES, } from "../index.js";
|
|
19
|
+
import { ALL_DESC, AREA_FILTER_DESC, AREA_LIMIT_DESC, AREA_PREVIEW_LIMIT, blockedCode, buildUpdatePatch, capabilitiesTable, DATE_FORMAT, DEFAULT_LIST_LIMIT, diagnose, FILTER_CONTRACT, FULL_DESC, hasTagPresence, isLooseRef, isValidTimeZone, LIMIT_DESC, looseShadowNotice, MCP_UPDATE_LABELS, MCP_WHEN_LABELS, mutationWireData, NOTES_FORMAT, noUuidMatch, omitEmpty, OMIT_EMPTY_NOTE, OP_ID_RE, opResult, OPERATION_KINDS, openThings, readAllowed, readCapability, uiAllowed, uiCapability, writeAllowed, writeCapability, PKG_VERSION, PROJECT_LIMIT_DESC, PROJECT_PREVIEW_LIMIT, REF_FORMAT, REF_RULE_NOTE, ReferenceResolutionError, REMINDER_FORMAT, RESOLUTION_DATE_FORMAT, schemaWarnings, shapeReadPayload, withTodayBucketTotals, withAreaBucketTotals, withSectionTotals, withUpcomingBlockTotals, splitWhenSugar, tagFilterFields, tagFlagConflict, validateViewArgs, verifyFailedCode, WHEN_VALUES, } from "../index.js";
|
|
20
|
+
/** Tools that mutate through the app. Everything unlisted is a plain read. */
|
|
21
|
+
const WRITE_TOOLS = new Set([
|
|
22
|
+
"add_todo",
|
|
23
|
+
"update",
|
|
24
|
+
"set_status",
|
|
25
|
+
"move_todo",
|
|
26
|
+
"set_tags",
|
|
27
|
+
"edit_checklist",
|
|
28
|
+
"delete",
|
|
29
|
+
"restore_item",
|
|
30
|
+
"heading",
|
|
31
|
+
"clear_reminder",
|
|
32
|
+
"duplicate_item",
|
|
33
|
+
"clone_item",
|
|
34
|
+
"add_project",
|
|
35
|
+
"move_project",
|
|
36
|
+
"add_area",
|
|
37
|
+
"add_tag",
|
|
38
|
+
"log_now",
|
|
39
|
+
"run_operation",
|
|
40
|
+
"batch",
|
|
41
|
+
"reorder",
|
|
42
|
+
"undo",
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* Tools with NO headless spelling at all — every path through them drives the
|
|
46
|
+
* Things window. A tool that merely *can* reach a GUI op (`heading`'s promote,
|
|
47
|
+
* `reorder`'s areas) is NOT listed: those have working headless branches, and
|
|
48
|
+
* the pipeline's own Article IV gate refuses the GUI ones per call.
|
|
49
|
+
*/
|
|
50
|
+
const UI_TOOLS = new Set(["repeat", "convert_to_project"]);
|
|
51
|
+
/** The always-available diagnostics. */
|
|
52
|
+
const DIAGNOSTIC_TOOLS = new Set(["doctor", "capabilities", "op_result"]);
|
|
53
|
+
function toolClass(name) {
|
|
54
|
+
if (DIAGNOSTIC_TOOLS.has(name))
|
|
55
|
+
return "none";
|
|
56
|
+
if (UI_TOOLS.has(name))
|
|
57
|
+
return "ui";
|
|
58
|
+
if (WRITE_TOOLS.has(name))
|
|
59
|
+
return "write";
|
|
60
|
+
return "read";
|
|
61
|
+
}
|
|
62
|
+
/** The one remediation every baked refusal ends with. */
|
|
63
|
+
const RESTART_REMEDIATION = "this server surveyed its permissions once at startup and does not re-check them, because a " +
|
|
64
|
+
"grant made now would attach to whoever launched it: stop this server, run the setup command " +
|
|
65
|
+
"above at the machine, then start it again.";
|
|
66
|
+
/** The vector classes this server can actually serve, for the instructions. */
|
|
67
|
+
function availableClasses(capability) {
|
|
68
|
+
const available = [];
|
|
69
|
+
if (readAllowed(capability.read))
|
|
70
|
+
available.push("read");
|
|
71
|
+
if (writeAllowed(capability.write))
|
|
72
|
+
available.push("write");
|
|
73
|
+
if (uiAllowed(capability.ui))
|
|
74
|
+
available.push("gui-driving");
|
|
75
|
+
return available;
|
|
76
|
+
}
|
|
20
77
|
function jsonResult(data) {
|
|
21
78
|
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
22
79
|
}
|
|
@@ -341,9 +398,11 @@ const preserveModifiedShape = {
|
|
|
341
398
|
/**
|
|
342
399
|
* The per-call idempotency key for a single write tool — the analogue of a batch
|
|
343
400
|
* line's op_id. A resubmission carrying the same key is recognized as already
|
|
344
|
-
* applied (a prior
|
|
345
|
-
*
|
|
346
|
-
*
|
|
401
|
+
* applied (a prior change recorded under that key) and is not re-run. Spread
|
|
402
|
+
* into every write tool whose call records ONE result — the single mutations,
|
|
403
|
+
* and the multi-step verbs that record one summary (the repeating-series tool,
|
|
404
|
+
* the template-target status/exception writes) — but not the variadic
|
|
405
|
+
* move/reorder tools, whose idempotency is the batch-shaped per-line op_id.
|
|
347
406
|
*/
|
|
348
407
|
const opIdShape = {
|
|
349
408
|
op_id: z
|
|
@@ -402,7 +461,7 @@ const tagLabel = (t) => t.parent === null ? t.title : `${t.parent} > ${t.title}`
|
|
|
402
461
|
* names without a discovery round-trip. Degrades to conventions-only when
|
|
403
462
|
* the database is not readable.
|
|
404
463
|
*/
|
|
405
|
-
function buildInstructions(getClient) {
|
|
464
|
+
function buildInstructions(getClient, capability) {
|
|
406
465
|
const lines = [
|
|
407
466
|
"This server reads and modifies the user's Things 3 data: to-dos, projects, areas, and tags.",
|
|
408
467
|
"",
|
|
@@ -418,6 +477,8 @@ function buildInstructions(getClient) {
|
|
|
418
477
|
`- Scheduling vocabulary: when = ${WHEN_VALUES}; deadlines are ${DATE_FORMAT}; reminders ` +
|
|
419
478
|
`are ${REMINDER_FORMAT}. Resolve relative calendar phrases against the Calendar context ` +
|
|
420
479
|
`below (or a date-sensitive read result's meta.clock.today), then pass the explicit date.`,
|
|
480
|
+
`- Notes vocabulary: the notes field is ${NOTES_FORMAT}. Newlines are kept as written, so ` +
|
|
481
|
+
`pass a multi-line body directly rather than flattening it into one line.`,
|
|
421
482
|
"- Every write tool accepts dry_run: true to preview the change without applying it. " +
|
|
422
483
|
"A preview creates no state, so later calls cannot reference an item that only appeared in " +
|
|
423
484
|
"a dry-run result. Operations with cascading or permanent effects require the explicit " +
|
|
@@ -430,6 +491,27 @@ function buildInstructions(getClient) {
|
|
|
430
491
|
"- For capped reads, pass limit to cap rows or all: true for everything; if both are set, all wins.",
|
|
431
492
|
`- Read results are compact: ${OMIT_EMPTY_NOTE}`,
|
|
432
493
|
];
|
|
494
|
+
// What this server may do, decided once at startup and true for its whole
|
|
495
|
+
// life (permissions doctrine, Article II). Stated up front so an agent plans
|
|
496
|
+
// around the real surface instead of discovering it one refusal at a time.
|
|
497
|
+
const available = availableClasses(capability);
|
|
498
|
+
const missing = [];
|
|
499
|
+
if (!readAllowed(capability.read))
|
|
500
|
+
missing.push(`reads — ${capability.read.detail}`);
|
|
501
|
+
if (!writeAllowed(capability.write))
|
|
502
|
+
missing.push(`changes — ${capability.write.detail}`);
|
|
503
|
+
if (!uiAllowed(capability.ui))
|
|
504
|
+
missing.push(`GUI-driven operations — ${capability.ui.detail}`);
|
|
505
|
+
lines.push("", "Permissions (checked once at server start, fixed for this server's lifetime):", `- Available here: ${available.length > 0 ? available.join(", ") : "nothing — every tool below refuses"}.`, ...(missing.length > 0
|
|
506
|
+
? [
|
|
507
|
+
`- Unavailable here: ${missing.join("; ")}.`,
|
|
508
|
+
"- Tools needing an unavailable capability refuse immediately rather than trying and " +
|
|
509
|
+
"raising a macOS permission dialog nobody is present to answer. Remediation for each " +
|
|
510
|
+
"is in its refusal. These grants attach to whichever process launched this server, so " +
|
|
511
|
+
"they cannot change while it runs: the fix is always stop the server, run the named " +
|
|
512
|
+
"setup command at the machine, start it again.",
|
|
513
|
+
]
|
|
514
|
+
: ["- Nothing is missing; no tool will refuse for want of a permission."]));
|
|
433
515
|
try {
|
|
434
516
|
const c = getClient();
|
|
435
517
|
// Under a container scope the inventory below is already limited to in-scope
|
|
@@ -499,7 +581,86 @@ export function createThingsMcpServer(options = {}) {
|
|
|
499
581
|
});
|
|
500
582
|
return client;
|
|
501
583
|
};
|
|
502
|
-
|
|
584
|
+
// THE STARTUP BAKE (permissions doctrine, Article II). One prompt-free survey
|
|
585
|
+
// before the transport is connected; the verdict then shapes the instructions
|
|
586
|
+
// and gates the tools, and is never re-taken.
|
|
587
|
+
const capability = options.capability ?? {
|
|
588
|
+
read: readCapability(options.dbPath !== undefined ? { dbPath: options.dbPath } : {}),
|
|
589
|
+
// SURVEY: starting a server must never start the user's app as a side
|
|
590
|
+
// effect. A closed Things reads as the liveness state; the write gate wakes
|
|
591
|
+
// it when a change is actually dispatched (#617).
|
|
592
|
+
write: writeCapability(),
|
|
593
|
+
ui: uiCapability(),
|
|
594
|
+
};
|
|
595
|
+
const warn = options.onStartupWarning ?? ((line) => process.stderr.write(`${line}\n`));
|
|
596
|
+
const unavailable = [
|
|
597
|
+
...(readAllowed(capability.read) ? [] : ["reads"]),
|
|
598
|
+
...(writeAllowed(capability.write) ? [] : ["changes"]),
|
|
599
|
+
...(uiAllowed(capability.ui) ? [] : ["GUI-driven operations"]),
|
|
600
|
+
];
|
|
601
|
+
if (unavailable.length > 0) {
|
|
602
|
+
warn(`things-api MCP: ${unavailable.join(", ")} unavailable on this machine — ` +
|
|
603
|
+
`${capability.read.detail}; ${capability.write.detail}; ${capability.ui.detail}. ` +
|
|
604
|
+
"Tools needing them refuse; restart this server after running setup.");
|
|
605
|
+
}
|
|
606
|
+
const server = new McpServer({ name: "things-api", version: PKG_VERSION }, { instructions: buildInstructions(getClient, capability) });
|
|
607
|
+
/**
|
|
608
|
+
* The baked refusal for one tool class, or null when the class is served.
|
|
609
|
+
* Article II in one function: the answer is the STARTUP verdict, quoted, with
|
|
610
|
+
* the setup command and the restart rule — never a fresh probe, and never a
|
|
611
|
+
* dispatch that could put a dialog on screen.
|
|
612
|
+
*/
|
|
613
|
+
const bakedRefusal = (name) => {
|
|
614
|
+
switch (toolClass(name)) {
|
|
615
|
+
case "none":
|
|
616
|
+
return null;
|
|
617
|
+
case "read":
|
|
618
|
+
return readAllowed(capability.read)
|
|
619
|
+
? null
|
|
620
|
+
: errorResult({
|
|
621
|
+
code: "environment",
|
|
622
|
+
message: `this tool reads the Things database, and ${capability.read.detail}`,
|
|
623
|
+
remediation: [...capability.read.remediation, RESTART_REMEDIATION].join("; "),
|
|
624
|
+
});
|
|
625
|
+
case "write":
|
|
626
|
+
// A write read-verifies, so it needs both classes; name the one that
|
|
627
|
+
// is actually missing rather than a generic "cannot write".
|
|
628
|
+
if (!readAllowed(capability.read)) {
|
|
629
|
+
return errorResult({
|
|
630
|
+
code: "environment",
|
|
631
|
+
message: `this tool changes Things data and verifies the result by reading it back, and ${capability.read.detail}`,
|
|
632
|
+
remediation: [...capability.read.remediation, RESTART_REMEDIATION].join("; "),
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
return writeAllowed(capability.write)
|
|
636
|
+
? null
|
|
637
|
+
: errorResult({
|
|
638
|
+
code: "environment",
|
|
639
|
+
message: `this tool changes Things data by driving the app, and ${capability.write.detail}`,
|
|
640
|
+
remediation: [...capability.write.remediation, RESTART_REMEDIATION].join("; "),
|
|
641
|
+
});
|
|
642
|
+
case "ui":
|
|
643
|
+
return uiAllowed(capability.ui)
|
|
644
|
+
? null
|
|
645
|
+
: errorResult({
|
|
646
|
+
code: "environment",
|
|
647
|
+
message: `this tool is delivered by driving the Things window, and ${capability.ui.detail}`,
|
|
648
|
+
remediation: [...capability.ui.remediation, RESTART_REMEDIATION].join("; "),
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* `server.registerTool` with the baked gate in front of every callback. Every
|
|
654
|
+
* registration below goes through this, so a tool cannot be added without a
|
|
655
|
+
* class — {@link toolClass} defaults an unlisted name to `read`, the weakest
|
|
656
|
+
* useful assumption, rather than to ungated.
|
|
657
|
+
*/
|
|
658
|
+
const registerTool = ((name, config, cb) => server.registerTool(name, config, ((...args) => {
|
|
659
|
+
const refusal = bakedRefusal(name);
|
|
660
|
+
if (refusal !== null)
|
|
661
|
+
return refusal;
|
|
662
|
+
return cb(...args);
|
|
663
|
+
})));
|
|
503
664
|
// The audit author for every write on this connection, derived once from the
|
|
504
665
|
// client's handshake identity (clientInfo.name). Read per call: clientInfo is
|
|
505
666
|
// populated when the initialize handshake completes, before any tool can run.
|
|
@@ -608,14 +769,18 @@ export function createThingsMcpServer(options = {}) {
|
|
|
608
769
|
return item.type;
|
|
609
770
|
};
|
|
610
771
|
// ------------------------------------------------------------------ reads
|
|
611
|
-
|
|
772
|
+
registerTool("read_view", {
|
|
612
773
|
description: "Read a Things list as the app presents it: today (two children buckets — " +
|
|
613
774
|
"children.today and children.evening, evening expires daily; the whole-view " +
|
|
614
775
|
"count due/overdue vs. other rides the result's second block), inbox, anytime, " +
|
|
615
776
|
"upcoming, someday, logbook, trash, or deadlines (a flat items list of everything " +
|
|
616
777
|
"carrying a deadline — to-dos and projects — in deadline order, most-overdue first; " +
|
|
617
778
|
"repeating items appear at their next occurrence's projected deadline; scope with " +
|
|
618
|
-
"today/overdue/project/area/tag)
|
|
779
|
+
"today/overdue/project/area/tag), or repeaters (every repeating series in the " +
|
|
780
|
+
"library — to-dos and projects — each carrying its decoded rule under " +
|
|
781
|
+
"repeating.rule; series appear in no other view, which show the occurrences a " +
|
|
782
|
+
"series spawns instead; paused and ended series are included; ordered by next " +
|
|
783
|
+
"occurrence, the ones with none last). For upcoming, " +
|
|
619
784
|
"horizon > 1 also includes future occurrences of repeating items (up to 10 each). " +
|
|
620
785
|
"anytime/someday return sections in canonical order (area + items; null area = the " +
|
|
621
786
|
"top-level block); children of someday/future-scheduled projects are excluded " +
|
|
@@ -639,6 +804,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
639
804
|
"logbook",
|
|
640
805
|
"trash",
|
|
641
806
|
"deadlines",
|
|
807
|
+
"repeaters",
|
|
642
808
|
]),
|
|
643
809
|
...tagFilterShape,
|
|
644
810
|
...tzShape,
|
|
@@ -854,10 +1020,14 @@ export function createThingsMcpServer(options = {}) {
|
|
|
854
1020
|
});
|
|
855
1021
|
return truncatedResult(shapeReadPayload("deadlines", items, full, c.refPromoter()), truncation);
|
|
856
1022
|
}
|
|
1023
|
+
case "repeaters": {
|
|
1024
|
+
const { items, truncation } = c.read.repeaters({ ...filter, ...zone, limit });
|
|
1025
|
+
return truncatedResult(shapeReadPayload("repeaters", items, full, c.refPromoter()), truncation);
|
|
1026
|
+
}
|
|
857
1027
|
}
|
|
858
1028
|
}, args.tz, () => (filterMeta !== undefined ? { filter: filterMeta } : undefined), () => looseAreaWarnings(getClient(), args.area));
|
|
859
1029
|
});
|
|
860
|
-
|
|
1030
|
+
registerTool("search", {
|
|
861
1031
|
description: "Find items by title/notes substring. Returns open, untrashed items by default; " +
|
|
862
1032
|
"include more with logged/trashed/all. Scope with project/area/tag — scope " +
|
|
863
1033
|
"references must name existing items. " +
|
|
@@ -920,7 +1090,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
920
1090
|
});
|
|
921
1091
|
return truncatedResult(shapeReadPayload("search", items, args.full === true, getClient().refPromoter()), truncation);
|
|
922
1092
|
}, args.tz, undefined, () => looseAreaWarnings(getClient(), args.area)));
|
|
923
|
-
|
|
1093
|
+
registerTool("changes_since", {
|
|
924
1094
|
description: "List items created or modified since a moment — including trashed, logged, and " +
|
|
925
1095
|
"repeating items (inspect each item's fields to tell them apart). Edits to tags, " +
|
|
926
1096
|
"areas, and checklist items do not mark the containing item as modified. " +
|
|
@@ -950,7 +1120,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
950
1120
|
});
|
|
951
1121
|
return truncatedResult(shapeReadPayload("changes", items, args.full === true, getClient().refPromoter()), truncation);
|
|
952
1122
|
}, args.tz));
|
|
953
|
-
|
|
1123
|
+
registerTool("get_item", {
|
|
954
1124
|
description: "Full detail for one item by uuid: notes, schedule, reminder, deadline, tags " +
|
|
955
1125
|
"(direct and inherited), checklist with per-item state, repeat schedule, and its " +
|
|
956
1126
|
"project/area/heading. " +
|
|
@@ -965,7 +1135,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
965
1135
|
? errorResult({ code: "not-found", message: noUuidMatch("item", args.uuid) })
|
|
966
1136
|
: readResult(shapeReadPayload("detail", item, false, getClient().refPromoter()));
|
|
967
1137
|
}));
|
|
968
|
-
|
|
1138
|
+
registerTool("get_project", {
|
|
969
1139
|
description: "One project's full contents: metadata plus its to-dos grouped under their headings. " +
|
|
970
1140
|
"Open headings (and archived ones not yet swept to the logbook) stay live, carrying " +
|
|
971
1141
|
"archived once archived; a swept archived heading moves into the logged region as a " +
|
|
@@ -997,7 +1167,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
997
1167
|
...(args.tz !== undefined && { zone: args.tz }),
|
|
998
1168
|
}), args.full === true, getClient().refPromoter()));
|
|
999
1169
|
}, args.tz));
|
|
1000
|
-
|
|
1170
|
+
registerTool("get_area", {
|
|
1001
1171
|
description: "One area's contents: metadata plus its direct to-dos (active first), its " +
|
|
1002
1172
|
"projects in canonical order, and later (scheduled/repeating/someday). The area " +
|
|
1003
1173
|
"logbook is not returned here — read it with read_view logbook + area; trashed " +
|
|
@@ -1061,7 +1231,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1061
1231
|
return groupedResult(withAreaBucketTotals(shapeReadPayload("area-view", view, args.full === true, getClient().refPromoter()), totals), truncation);
|
|
1062
1232
|
}, args.tz, undefined, () => (areaNotice !== undefined ? [areaNotice] : undefined));
|
|
1063
1233
|
});
|
|
1064
|
-
|
|
1234
|
+
registerTool("list_collections", {
|
|
1065
1235
|
description: "List every project, area, or tag (tags include their parent-tag nesting). Use to " +
|
|
1066
1236
|
"refresh the inventory summarized in the server instructions. The tag filters scope " +
|
|
1067
1237
|
"the projects list by each project's own tags (areas/tags reject them). " +
|
|
@@ -1116,7 +1286,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1116
1286
|
}, args.tz));
|
|
1117
1287
|
// ---------------------------------------------------------------- to-dos
|
|
1118
1288
|
const whenSchema = z.string().optional().describe(WHEN_VALUES);
|
|
1119
|
-
|
|
1289
|
+
registerTool("add_todo", {
|
|
1120
1290
|
description: "Create a to-do and return its uuid. Optionally schedule it, set a reminder or " +
|
|
1121
1291
|
"deadline, tag it, give it a checklist, and place it in a project or area " +
|
|
1122
1292
|
"(optionally under a heading within that project). A reminder " +
|
|
@@ -1124,7 +1294,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1124
1294
|
"project reopens that project — pass acknowledge_project_reopen to confirm.",
|
|
1125
1295
|
inputSchema: {
|
|
1126
1296
|
title: z.string(),
|
|
1127
|
-
notes: z.string().optional(),
|
|
1297
|
+
notes: z.string().optional().describe(`Notes body — ${NOTES_FORMAT}`),
|
|
1128
1298
|
when: whenSchema,
|
|
1129
1299
|
reminder: z.string().optional().describe(REMINDER_FORMAT),
|
|
1130
1300
|
deadline: z.string().optional().describe(DATE_FORMAT),
|
|
@@ -1177,7 +1347,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1177
1347
|
...(args.completed_at !== undefined && { completedAt: args.completed_at }),
|
|
1178
1348
|
}, writeOptions(args)));
|
|
1179
1349
|
}));
|
|
1180
|
-
|
|
1350
|
+
registerTool("update", {
|
|
1181
1351
|
description: "Edit an existing to-do, project, area, or tag — kind selects which. " +
|
|
1182
1352
|
"kind todo/project: title, notes (or append_notes/prepend_notes to add a line to the " +
|
|
1183
1353
|
"existing body, exclusive with notes), schedule (when), reminder/clear_reminder, and " +
|
|
@@ -1198,7 +1368,10 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1198
1368
|
.describe("The item to update — a to-do by uuid; a project, area, or tag also accepts a " +
|
|
1199
1369
|
"unique name"),
|
|
1200
1370
|
title: z.string().optional().describe("New title (any kind)"),
|
|
1201
|
-
notes: z
|
|
1371
|
+
notes: z
|
|
1372
|
+
.string()
|
|
1373
|
+
.optional()
|
|
1374
|
+
.describe(`todo/project: replaces the whole notes body — ${NOTES_FORMAT}`),
|
|
1202
1375
|
append_notes: z.string().optional().describe("todo/project: add a line after the notes"),
|
|
1203
1376
|
prepend_notes: z.string().optional().describe("todo/project: add a line before the notes"),
|
|
1204
1377
|
when: whenSchema,
|
|
@@ -1220,6 +1393,17 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1220
1393
|
.array(z.string())
|
|
1221
1394
|
.optional()
|
|
1222
1395
|
.describe(`area: replace the tag set (full) — ${TAG_REF_FORMAT}`),
|
|
1396
|
+
exception: z
|
|
1397
|
+
.boolean()
|
|
1398
|
+
.optional()
|
|
1399
|
+
.describe("todo, repeating only: change just the NEXT occurrence and leave the series alone " +
|
|
1400
|
+
"(the occurrence is created if it has not appeared yet). Refused when the series " +
|
|
1401
|
+
"already lands on the requested day, and when the series names no next date at " +
|
|
1402
|
+
"all (a paused one — resume it first). The result's occurrence field names both the occurrence " +
|
|
1403
|
+
"and the series. Each call takes another occurrence out of the series, so pass " +
|
|
1404
|
+
"op_id when retrying: a resubmission with the same key replays the first result " +
|
|
1405
|
+
"instead of creating a second occurrence. Undo restores the occurrence's own " +
|
|
1406
|
+
"change but cannot remove the occurrence or rewind the series"),
|
|
1223
1407
|
parent: z.string().optional().describe("tag: existing tag to nest under"),
|
|
1224
1408
|
unnest: z.boolean().optional().describe("tag: move the tag to the top level"),
|
|
1225
1409
|
shortcut: z.string().optional().describe("tag: keyboard shortcut character"),
|
|
@@ -1238,49 +1422,23 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1238
1422
|
const opts = writeOptions(args);
|
|
1239
1423
|
const c = getClient();
|
|
1240
1424
|
if (args.kind === "todo" || args.kind === "project") {
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
if (
|
|
1246
|
-
return usage(
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
return usage(sugar.message);
|
|
1255
|
-
const when = sugar.kind === "split" ? sugar.when : args.when;
|
|
1256
|
-
const reminder = sugar.kind === "split" ? sugar.reminder : args.reminder;
|
|
1257
|
-
return mutationResult(await c.write.updateTodo(args.uuid, {
|
|
1258
|
-
...(args.title !== undefined && { title: args.title }),
|
|
1259
|
-
...(args.notes !== undefined && { notes: args.notes }),
|
|
1260
|
-
...(args.append_notes !== undefined && { appendNotes: args.append_notes }),
|
|
1261
|
-
...(args.prepend_notes !== undefined && { prependNotes: args.prepend_notes }),
|
|
1262
|
-
...(when !== undefined && { when: when }),
|
|
1263
|
-
...(reminder !== undefined && { reminder }),
|
|
1264
|
-
...(args.clear_reminder === true && { reminder: null }),
|
|
1265
|
-
...(args.deadline !== undefined && { deadline: args.deadline }),
|
|
1266
|
-
...(args.clear_deadline === true && { deadline: null }),
|
|
1267
|
-
...(args.created_at !== undefined && { createdAt: args.created_at }),
|
|
1268
|
-
...(args.completed_at !== undefined && { completedAt: args.completed_at }),
|
|
1269
|
-
}, opts));
|
|
1425
|
+
// The patch — and every exclusive pair in the vocabulary, the `@time`
|
|
1426
|
+
// sugar included — comes from the ONE builder the CLI shares, so the two
|
|
1427
|
+
// surfaces cannot drift on which flags they accept (#491 doctrine).
|
|
1428
|
+
const built = buildUpdatePatch(args, MCP_UPDATE_LABELS);
|
|
1429
|
+
if (built.kind === "error")
|
|
1430
|
+
return usage(built.message);
|
|
1431
|
+
if (args.exception === true) {
|
|
1432
|
+
if (args.kind !== "todo")
|
|
1433
|
+
return usage("exception applies to kind todo");
|
|
1434
|
+
if (args.created_at !== undefined || args.completed_at !== undefined) {
|
|
1435
|
+
return usage("exception cannot be combined with created_at/completed_at");
|
|
1436
|
+
}
|
|
1437
|
+
return mutationResult(await c.write.updateTodoOccurrence(args.uuid, built.patch, opts));
|
|
1270
1438
|
}
|
|
1271
|
-
return mutationResult(
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
...(args.append_notes !== undefined && { appendNotes: args.append_notes }),
|
|
1275
|
-
...(args.prepend_notes !== undefined && { prependNotes: args.prepend_notes }),
|
|
1276
|
-
...(args.when !== undefined && { when: args.when }),
|
|
1277
|
-
...(args.reminder !== undefined && { reminder: args.reminder }),
|
|
1278
|
-
...(args.clear_reminder === true && { reminder: null }),
|
|
1279
|
-
...(args.deadline !== undefined && { deadline: args.deadline }),
|
|
1280
|
-
...(args.clear_deadline === true && { deadline: null }),
|
|
1281
|
-
...(args.created_at !== undefined && { createdAt: args.created_at }),
|
|
1282
|
-
...(args.completed_at !== undefined && { completedAt: args.completed_at }),
|
|
1283
|
-
}, opts));
|
|
1439
|
+
return mutationResult(args.kind === "todo"
|
|
1440
|
+
? await c.write.updateTodo(args.uuid, built.patch, opts)
|
|
1441
|
+
: await c.write.updateProject(args.uuid, built.patch, opts));
|
|
1284
1442
|
}
|
|
1285
1443
|
if (args.kind === "area") {
|
|
1286
1444
|
if (args.title === undefined && args.tags === undefined) {
|
|
@@ -1313,9 +1471,15 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1313
1471
|
...(args.clear_shortcut === true && { clearShortcut: true }),
|
|
1314
1472
|
}, opts));
|
|
1315
1473
|
}));
|
|
1316
|
-
|
|
1474
|
+
registerTool("set_status", {
|
|
1317
1475
|
description: "Set a to-do's or project's status (scope selects which): completed, canceled, or open " +
|
|
1318
|
-
"(reopening a completed/canceled item).
|
|
1476
|
+
"(reopening a completed/canceled item). scope todo, a REPEATING to-do: completed/canceled " +
|
|
1477
|
+
"resolves the series' CURRENT occurrence — the unfinished one if there is one, otherwise " +
|
|
1478
|
+
"the next one, created for the purpose — and leaves the series running; the result's " +
|
|
1479
|
+
"occurrence field names both uuids and says which of the two was created. Each call is a " +
|
|
1480
|
+
"new resolution that takes the FOLLOWING occurrence, so pass op_id when retrying: a " +
|
|
1481
|
+
"resubmission with the same key replays the first result instead of creating a second " +
|
|
1482
|
+
"occurrence. Reopening a repeating to-do is not available. " +
|
|
1319
1483
|
"scope project, completing or canceling requires a children policy: 'require-resolved' " +
|
|
1320
1484
|
"errors if open to-dos remain; 'auto-complete'/'auto-cancel' resolves them together with " +
|
|
1321
1485
|
"the project (canceling never alters already-completed children). scope project, status " +
|
|
@@ -1401,7 +1565,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1401
1565
|
? jsonResult(outcome)
|
|
1402
1566
|
: mutationResult(outcome.project);
|
|
1403
1567
|
}));
|
|
1404
|
-
|
|
1568
|
+
registerTool("move_todo", {
|
|
1405
1569
|
description: "Move one or more to-dos as an ordered block (spec §4). MOVE changes WHAT a to-do " +
|
|
1406
1570
|
"belongs to (membership somewhere); to REARRANGE to-dos that already share a container " +
|
|
1407
1571
|
"without changing membership, call this with a position (first/last/before/after) and " +
|
|
@@ -1474,7 +1638,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1474
1638
|
};
|
|
1475
1639
|
return moveResult(await getClient().write.moveTodos(request, writeOptions(args)));
|
|
1476
1640
|
}));
|
|
1477
|
-
|
|
1641
|
+
registerTool("set_tags", {
|
|
1478
1642
|
description: "Replace or extend a to-do's or project's tags. mode 'replace' (default) sets exactly " +
|
|
1479
1643
|
"the given list — an empty list removes all tags; mode 'add' merges with the current " +
|
|
1480
1644
|
"tags. Tags must exist unless create_tags is set (or create them first with add_tag).",
|
|
@@ -1501,7 +1665,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1501
1665
|
? await c.write.setProjectTags(args.uuid, args.tags, opts)
|
|
1502
1666
|
: await c.write.setTags(args.uuid, args.tags, opts));
|
|
1503
1667
|
}));
|
|
1504
|
-
|
|
1668
|
+
registerTool("edit_checklist", {
|
|
1505
1669
|
description: "Edit a to-do's checklist. The single-item actions add / remove / check / uncheck " +
|
|
1506
1670
|
"/ rename / move change one item — targeted by title or 1-based index — and leave " +
|
|
1507
1671
|
"every other item and its checked state untouched (duplicate titles resolve " +
|
|
@@ -1591,11 +1755,16 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1591
1755
|
return mutationResult(await c.write.editChecklist(args.uuid, edit, writeOptions(args)));
|
|
1592
1756
|
}));
|
|
1593
1757
|
// ------------------------------------------------- to-dos AND projects
|
|
1594
|
-
|
|
1758
|
+
registerTool("delete", {
|
|
1595
1759
|
description: "Delete a to-do/project (kind item), an area, or a tag — kind selects which. " +
|
|
1596
1760
|
"kind item: moves a to-do or project to the Trash (recoverable via restore_item until " +
|
|
1597
|
-
"the Trash is emptied; a deleted project takes its to-dos with it
|
|
1598
|
-
"
|
|
1761
|
+
"the Trash is emptied; a deleted project takes its to-dos with it). Deleting a repeating " +
|
|
1762
|
+
"series' TEMPLATE is allowed and ends the series: it stops generating new occurrences and " +
|
|
1763
|
+
"its existing occurrences are left in place (the result names how many, and the current " +
|
|
1764
|
+
"one). That one cannot be undone — neither undo nor restore_item brings a template back, " +
|
|
1765
|
+
"so no undo token is returned; the series returns only via Put Back in the Things app's " +
|
|
1766
|
+
"Trash. Deleting a single occurrence leaves the series running. kind area: PERMANENT — " +
|
|
1767
|
+
"areas do not go to the Trash, so this cannot " +
|
|
1599
1768
|
"be undone and requires dangerously_permanent; deleting an area moves its to-dos and " +
|
|
1600
1769
|
"projects to the Trash, so a non-empty area is refused unless you pass " +
|
|
1601
1770
|
"allow_non_empty_area (empty it first to keep its contents). kind tag: PERMANENT — requires " +
|
|
@@ -1637,7 +1806,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1637
1806
|
return mutationResult(await c.write.deleteTag(args.uuid, opts));
|
|
1638
1807
|
}
|
|
1639
1808
|
}));
|
|
1640
|
-
|
|
1809
|
+
registerTool("restore_item", {
|
|
1641
1810
|
description: "Restore a trashed to-do or project. A to-do returns to the Inbox without its " +
|
|
1642
1811
|
"previous schedule or project/area. A project is restored in place: its schedule, " +
|
|
1643
1812
|
"area, and children come back exactly as they were.",
|
|
@@ -1655,11 +1824,11 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1655
1824
|
// write tools: add_todo / add_project (Logbook import), update (rewrite an
|
|
1656
1825
|
// already-resolved item's timestamps), and set_status (backdate on
|
|
1657
1826
|
// complete/cancel) — matching the CLI's --created-at/--completed-at flags.
|
|
1658
|
-
|
|
1827
|
+
registerTool("heading", {
|
|
1659
1828
|
description: "Manage a project's headings — action selects which; project is always required, and a " +
|
|
1660
1829
|
"heading is selected by its exact title or its uuid (never an ordinal). add_heading: a " +
|
|
1661
1830
|
"new heading in the project (project + title; returns its uuid; uses the Things proxy " +
|
|
1662
|
-
"shortcuts, set up once with `things setup
|
|
1831
|
+
"shortcuts, set up once with `things setup`); a placement flag positions it, " +
|
|
1663
1832
|
"else it appends. rename_heading: rename in place (project + heading + title; works on " +
|
|
1664
1833
|
"archived headings). archive_heading: retire a heading so it leaves the active project " +
|
|
1665
1834
|
"view (reversible with unarchive_heading); with open children pass children — complete " +
|
|
@@ -1669,11 +1838,15 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1669
1838
|
"promote_heading: promote a heading into a new project — this REPLACES the heading and " +
|
|
1670
1839
|
"cannot be undone (its to-dos move under the new project), and requires " +
|
|
1671
1840
|
"dangerously_drive_gui. move_heading: reposition headings as an ordered block (children " +
|
|
1672
|
-
"follow); pass exactly one placement flag.
|
|
1841
|
+
"follow); pass exactly one placement flag. GUI-only (requires dangerously_drive_gui) — " +
|
|
1842
|
+
"Things offers heading order as keyboard shortcuts on a selected heading row and nowhere " +
|
|
1843
|
+
"else, so this moves one heading one slot at a time and re-reads the order after each " +
|
|
1844
|
+
"step; it refuses when the project holds a completed or canceled heading. " +
|
|
1673
1845
|
"move_heading_to_project: relocate ONE heading (with its to-dos) to a DIFFERENT project " +
|
|
1674
1846
|
"(project + heading + to_project) — the cross-project move, distinct from move_heading's " +
|
|
1675
1847
|
"within-project reorder; GUI-only (requires dangerously_drive_gui), fails closed on a " +
|
|
1676
|
-
"source-heading or destination-project title collision
|
|
1848
|
+
"source-heading or destination-project title collision and on a completed or canceled " +
|
|
1849
|
+
"destination project (reopen it first), and has no undo (move it back). " +
|
|
1677
1850
|
"dissolve_heading: remove a heading but KEEP its to-dos as direct project children (NOT " +
|
|
1678
1851
|
"trashed — the opposite of a delete cascade); GUI-only (requires dangerously_drive_gui), " +
|
|
1679
1852
|
"fails closed on a title collision, no undo.",
|
|
@@ -1820,7 +1993,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1820
1993
|
}
|
|
1821
1994
|
}
|
|
1822
1995
|
}));
|
|
1823
|
-
|
|
1996
|
+
registerTool("convert_to_project", {
|
|
1824
1997
|
description: "Promote a to-do into a project. This REPLACES the to-do with a new project (its notes " +
|
|
1825
1998
|
"are kept); the to-do's identity is gone and it cannot be undone. Requires " +
|
|
1826
1999
|
"dangerously_drive_gui. The new project's uuid is on the result. (To promote a HEADING, " +
|
|
@@ -1834,7 +2007,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1834
2007
|
},
|
|
1835
2008
|
annotations: DESTRUCTIVE,
|
|
1836
2009
|
}, async (args) => guard(async () => mutationResult(await getClient().write.run("todo.convert-to-project", { uuid: args.uuid }, writeOptions(args)))));
|
|
1837
|
-
|
|
2010
|
+
registerTool("clear_reminder", {
|
|
1838
2011
|
description: "Clear a to-do's time-of-day reminder while keeping its scheduled date. Uses the " +
|
|
1839
2012
|
"Things proxy shortcuts when installed (in place, and the only path for a repeating " +
|
|
1840
2013
|
"to-do); otherwise a non-repeating dated to-do falls back to a URL re-schedule that " +
|
|
@@ -1921,7 +2094,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1921
2094
|
fields.startDaysEarlier = a.start_days_earlier;
|
|
1922
2095
|
return fields;
|
|
1923
2096
|
};
|
|
1924
|
-
|
|
2097
|
+
registerTool("repeat", {
|
|
1925
2098
|
description: "Manage recurrence on a to-do or project (scope) by driving the local Things app's " +
|
|
1926
2099
|
"interface — every action needs dangerously_drive_gui. action start: turn a plain item " +
|
|
1927
2100
|
"into a repeating one (promote-via-clone: a disposable copy is promoted and the ORIGINAL " +
|
|
@@ -1934,7 +2107,9 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1934
2107
|
"pause/resume: stop or restart its new occurrences, keeping the rule. action add: create " +
|
|
1935
2108
|
"an item and make it repeating in one call — it is created first and PERSISTS even if the " +
|
|
1936
2109
|
"promote refuses (give a title; for a project give an area to place it or omit it to " +
|
|
1937
|
-
"create in Someday); undo removes the created series. Returns the new template's uuid."
|
|
2110
|
+
"create in Someday); undo removes the created series. Returns the new template's uuid. " +
|
|
2111
|
+
"Every start/add re-run makes ANOTHER series, so pass op_id when retrying: a resubmission " +
|
|
2112
|
+
"with the same key replays the first result instead of making a second series.",
|
|
1938
2113
|
inputSchema: {
|
|
1939
2114
|
scope: z.enum(["todo", "project"]),
|
|
1940
2115
|
action: z.enum(["start", "reschedule", "pause", "resume", "add"]),
|
|
@@ -1943,15 +2118,16 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1943
2118
|
.optional()
|
|
1944
2119
|
.describe("start/reschedule/pause/resume: the item (a project also accepts a unique name)"),
|
|
1945
2120
|
title: z.string().optional().describe("add: the new item's title"),
|
|
1946
|
-
notes: z.string().optional().describe(
|
|
2121
|
+
notes: z.string().optional().describe(`add: notes — ${NOTES_FORMAT}`),
|
|
1947
2122
|
area: z.string().optional().describe(`add (project): destination area (${REF_FORMAT})`),
|
|
1948
2123
|
project_deadline: z
|
|
1949
2124
|
.string()
|
|
1950
2125
|
.optional()
|
|
1951
|
-
.describe(`add: due date (${DATE_FORMAT}).
|
|
1952
|
-
`
|
|
1953
|
-
`
|
|
1954
|
-
`other, both must agree).
|
|
2126
|
+
.describe(`add: due date (${DATE_FORMAT}). It deadlines EVERY occurrence — each starts ` +
|
|
2127
|
+
`(deadline − when) days before its own deadline, so it needs a concrete when on or ` +
|
|
2128
|
+
`before it. For a to-do it is an alternative spelling of start_days_earlier (give ` +
|
|
2129
|
+
`one or the other, both must agree). Not available with after_completion on a ` +
|
|
2130
|
+
`project, whose occurrences are created without a deadline.`),
|
|
1955
2131
|
todos: z.array(z.string()).optional().describe("add (project): initial child to-do titles"),
|
|
1956
2132
|
frequency: z
|
|
1957
2133
|
.enum(["daily", "weekly", "monthly", "yearly"])
|
|
@@ -1978,6 +2154,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1978
2154
|
...driveGuiShape,
|
|
1979
2155
|
...dryRunShape,
|
|
1980
2156
|
...preserveModifiedShape,
|
|
2157
|
+
...opIdShape,
|
|
1981
2158
|
},
|
|
1982
2159
|
annotations: DESTRUCTIVE,
|
|
1983
2160
|
}, async (args) => guard(async () => {
|
|
@@ -1992,10 +2169,10 @@ export function createThingsMcpServer(options = {}) {
|
|
|
1992
2169
|
}
|
|
1993
2170
|
// add-repeating carries only the calendar-anchor rule fields here; the rule
|
|
1994
2171
|
// reminder is set with a follow-up reschedule. A concrete `project_deadline`
|
|
1995
|
-
// maps to the RULE
|
|
1996
|
-
// deadline-free)
|
|
1997
|
-
//
|
|
1998
|
-
//
|
|
2172
|
+
// maps to the RULE on BOTH scopes (DBLSPAWN1 — each occurrence deadlined,
|
|
2173
|
+
// the created item deadline-free). For a to-do, `start_days_earlier` is the
|
|
2174
|
+
// alternative spelling of that deadline geometry (the two must AGREE when
|
|
2175
|
+
// both are given — enforced in the library).
|
|
1999
2176
|
const { reminder: _reminder, deadline: _deadline, startDaysEarlier, ...ruleExtras } = repeatExtras(args, frequency);
|
|
2000
2177
|
if (args.scope === "todo") {
|
|
2001
2178
|
return mutationResult(await c.write.addRepeatingTodo({
|
|
@@ -2049,7 +2226,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2049
2226
|
// scope project, action start → the dedicated make-repeating method
|
|
2050
2227
|
return mutationResult(await c.write.makeRepeatingProject(args.uuid, { frequency, interval, ...extras }, opts));
|
|
2051
2228
|
}));
|
|
2052
|
-
|
|
2229
|
+
registerTool("duplicate_item", {
|
|
2053
2230
|
description: "Duplicate a to-do or project and return the copy's uuid; a duplicated project " +
|
|
2054
2231
|
"includes its children. Not available for repeating items.",
|
|
2055
2232
|
inputSchema: { uuid: z.string(), ...dryRunShape, ...preserveModifiedShape, ...opIdShape },
|
|
@@ -2060,7 +2237,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2060
2237
|
? await c.write.duplicateTodo(args.uuid, writeOptions(args))
|
|
2061
2238
|
: await c.write.duplicateProject(args.uuid, writeOptions(args)));
|
|
2062
2239
|
}));
|
|
2063
|
-
|
|
2240
|
+
registerTool("clone_item", {
|
|
2064
2241
|
description: "Clone a to-do or project — a faithful content copy through official write surfaces and " +
|
|
2065
2242
|
"return the clone's uuid. Copies title, notes, tags, when, reminder, deadline, checklist " +
|
|
2066
2243
|
"items and their checked state, container, and completed/canceled state with the exact " +
|
|
@@ -2097,12 +2274,12 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2097
2274
|
: await c.write.cloneProject(args.uuid, clone, writeOptions(args)));
|
|
2098
2275
|
}));
|
|
2099
2276
|
// -------------------------------------------------------------- projects
|
|
2100
|
-
|
|
2277
|
+
registerTool("add_project", {
|
|
2101
2278
|
description: "Create a project and return its uuid. Optionally place it in an area, schedule " +
|
|
2102
2279
|
"it, set a deadline, and seed it with initial to-dos.",
|
|
2103
2280
|
inputSchema: {
|
|
2104
2281
|
title: z.string(),
|
|
2105
|
-
notes: z.string().optional(),
|
|
2282
|
+
notes: z.string().optional().describe(`Notes body — ${NOTES_FORMAT}`),
|
|
2106
2283
|
area: z.string().optional().describe(`Destination area (${REF_FORMAT})`),
|
|
2107
2284
|
when: whenSchema,
|
|
2108
2285
|
deadline: z.string().optional().describe(DATE_FORMAT),
|
|
@@ -2137,7 +2314,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2137
2314
|
...(args.completed_at !== undefined && { completedAt: args.completed_at }),
|
|
2138
2315
|
}, writeOptions(args)));
|
|
2139
2316
|
}));
|
|
2140
|
-
|
|
2317
|
+
registerTool("move_project", {
|
|
2141
2318
|
description: "Move one or more projects as an ordered block (spec §4/§5). Pass at most one " +
|
|
2142
2319
|
"destination: to_area, or no_area (leave the area — a project's complete detach). To " +
|
|
2143
2320
|
"REORDER projects among their siblings without changing area, pass a position " +
|
|
@@ -2172,7 +2349,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2172
2349
|
return moveResult(await getClient().write.moveProjects(request, writeOptions(args)));
|
|
2173
2350
|
}));
|
|
2174
2351
|
// ----------------------------------------------------------------- areas
|
|
2175
|
-
|
|
2352
|
+
registerTool("add_area", {
|
|
2176
2353
|
description: "Create an area, optionally tagged. Tags must exist unless create_tags is set.",
|
|
2177
2354
|
inputSchema: {
|
|
2178
2355
|
title: z.string(),
|
|
@@ -2185,7 +2362,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2185
2362
|
annotations: NON_DESTRUCTIVE,
|
|
2186
2363
|
}, async (args) => guard(async () => mutationResult(await getClient().write.addArea({ title: args.title, ...(args.tags !== undefined && { tags: args.tags }) }, writeOptions(args)))));
|
|
2187
2364
|
// ------------------------------------------------------------------ tags
|
|
2188
|
-
|
|
2365
|
+
registerTool("add_tag", {
|
|
2189
2366
|
description: "Create a tag, optionally nested under an existing parent tag.",
|
|
2190
2367
|
inputSchema: {
|
|
2191
2368
|
title: z.string(),
|
|
@@ -2196,7 +2373,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2196
2373
|
},
|
|
2197
2374
|
annotations: NON_DESTRUCTIVE,
|
|
2198
2375
|
}, async (args) => guard(async () => mutationResult(await getClient().write.addTag({ title: args.title, ...(args.parent !== undefined && { parent: args.parent }) }, writeOptions(args)))));
|
|
2199
|
-
|
|
2376
|
+
registerTool("log_now", {
|
|
2200
2377
|
description: "Move completed items to the Logbook now. The result discloses how many items were moved " +
|
|
2201
2378
|
"(observed.logged); when none are waiting it logs nothing — a clean no-op, not an error. " +
|
|
2202
2379
|
"This cannot be undone.",
|
|
@@ -2204,10 +2381,13 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2204
2381
|
annotations: DESTRUCTIVE,
|
|
2205
2382
|
}, async (args) => guard(async () => mutationResult(await getClient().write.logNow(writeOptions(args)))));
|
|
2206
2383
|
// -------------------------------------------------- generic + discovery
|
|
2207
|
-
|
|
2384
|
+
registerTool("run_operation", {
|
|
2208
2385
|
description: "Run any cataloged operation by kind — the generic entry for operations without a " +
|
|
2209
2386
|
"dedicated tool (e.g. trash.empty). Call capabilities first for the catalog of " +
|
|
2210
|
-
"operation kinds and their parameter shapes
|
|
2387
|
+
"operation kinds and their parameter shapes (each entry carries a params list naming " +
|
|
2388
|
+
"every field, whether it is optional, and the shape it accepts). A malformed params " +
|
|
2389
|
+
"bag — an unknown field, a wrong type, a bare string where a container reference " +
|
|
2390
|
+
"object belongs — is refused before anything runs.",
|
|
2211
2391
|
inputSchema: {
|
|
2212
2392
|
op: z.enum(OPERATION_KINDS),
|
|
2213
2393
|
params: z
|
|
@@ -2233,11 +2413,13 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2233
2413
|
},
|
|
2234
2414
|
annotations: DESTRUCTIVE,
|
|
2235
2415
|
}, async (args) => guard(async () => mutationResult(await getClient().write.run(args.op, args.params, writeOptions(args)))));
|
|
2236
|
-
|
|
2416
|
+
registerTool("batch", {
|
|
2237
2417
|
description: "Run several operations in order, each independently — there are no transactions, " +
|
|
2238
2418
|
"and a failure does not roll back earlier operations. A statically-invalid operation " +
|
|
2239
|
-
"(
|
|
2240
|
-
"refuses the WHOLE batch before anything runs, naming
|
|
2419
|
+
"(unknown op, an unknown or wrongly-typed param, a $ref to an undeclared/forward " +
|
|
2420
|
+
"temp_id, a duplicate temp_id) refuses the WHOLE batch before anything runs, naming " +
|
|
2421
|
+
"every bad operation and the field it names. A container param takes an object " +
|
|
2422
|
+
'({"project": {"uuid": "…"}}), never a bare string. Otherwise ' +
|
|
2241
2423
|
"per-operation results return in order. By DEFAULT a runtime failure STOPS the batch " +
|
|
2242
2424
|
"(later operations reported not-run, with resume guidance in the summary); " +
|
|
2243
2425
|
"continue_on_error runs past failures. " +
|
|
@@ -2248,8 +2430,11 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2248
2430
|
"reference a tag by title) and unique per batch. IDEMPOTENCY: op_id makes resubmission " +
|
|
2249
2431
|
"safe — an operation matching an earlier success is reported already-applied, not " +
|
|
2250
2432
|
"re-created (put an op_id on EVERY operation so a stopped batch can be resubmitted " +
|
|
2251
|
-
"verbatim to resume).
|
|
2252
|
-
"
|
|
2433
|
+
"verbatim to resume). TIMELINE: preserve_modified applies to EVERY operation (an " +
|
|
2434
|
+
"operation may override it with its own options.preserve_modified), so a bulk re-tag " +
|
|
2435
|
+
"stays off the modification-date timeline under one undo token. The result adds " +
|
|
2436
|
+
"temp_id_mapping (handle → uuid) and undo_token, which reverses the whole batch as one " +
|
|
2437
|
+
"unit via the undo tool.",
|
|
2253
2438
|
inputSchema: {
|
|
2254
2439
|
ops: z
|
|
2255
2440
|
.array(z.object({
|
|
@@ -2271,6 +2456,11 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2271
2456
|
acknowledge_tag_subtree: z.boolean().optional(),
|
|
2272
2457
|
allow_non_empty_area: z.boolean().optional(),
|
|
2273
2458
|
dangerously_drive_gui: z.boolean().optional(),
|
|
2459
|
+
preserve_modified: z
|
|
2460
|
+
.boolean()
|
|
2461
|
+
.optional()
|
|
2462
|
+
.describe("Keep THIS operation off the modification-date timeline; overrides the " +
|
|
2463
|
+
"run-level preserve_modified (false opts one operation back on)"),
|
|
2274
2464
|
})
|
|
2275
2465
|
.optional(),
|
|
2276
2466
|
}))
|
|
@@ -2297,6 +2487,12 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2297
2487
|
...(o?.acknowledge_tag_subtree === true && { acknowledgeTagSubtree: true }),
|
|
2298
2488
|
...(o?.allow_non_empty_area === true && { allowNonEmptyArea: true }),
|
|
2299
2489
|
...(o?.dangerously_drive_gui === true && { dangerouslyDriveGui: true }),
|
|
2490
|
+
// Per-op override of the run-level preserve_modified; passed through
|
|
2491
|
+
// when EXPLICIT (including false, which opts one op back onto the
|
|
2492
|
+
// timeline) so the batch engine's `??` precedence sees it.
|
|
2493
|
+
...(o?.preserve_modified !== undefined && {
|
|
2494
|
+
preserveModified: o.preserve_modified,
|
|
2495
|
+
}),
|
|
2300
2496
|
...(ceiling !== undefined && { maxDisruption: ceiling }),
|
|
2301
2497
|
};
|
|
2302
2498
|
return {
|
|
@@ -2310,6 +2506,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2310
2506
|
const batchResult = await getClient().write.batch(ops, {
|
|
2311
2507
|
...(args.dry_run === true && { dryRun: true }),
|
|
2312
2508
|
...(args.continue_on_error === true && { continueOnError: true }),
|
|
2509
|
+
...(args.preserve_modified === true && { preserveModified: true }),
|
|
2313
2510
|
actor: mcpActor(),
|
|
2314
2511
|
});
|
|
2315
2512
|
// First block: the per-op results, each FLATTENED to the wire shape (a
|
|
@@ -2340,7 +2537,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2340
2537
|
],
|
|
2341
2538
|
};
|
|
2342
2539
|
}));
|
|
2343
|
-
|
|
2540
|
+
registerTool("reorder", {
|
|
2344
2541
|
description: "The ONE reorder tool — rearrange a single-KIND set IN PLACE: to-dos, projects, headings, " +
|
|
2345
2542
|
"OR sidebar areas. This REARRANGES, never changes what an item belongs to (to change " +
|
|
2346
2543
|
"membership use move_todo / move_project). All refs must be one kind; only to-dos and " +
|
|
@@ -2350,9 +2547,11 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2350
2547
|
"assembles the named items as a block at the EARLIEST one's current slot; start/end/" +
|
|
2351
2548
|
"before/after position the block. A Today or This Evening member also holds a slot in its " +
|
|
2352
2549
|
"container, so a set coherent on BOTH axes is ambiguous — pass `in` to name the axis. " +
|
|
2353
|
-
"HEADINGS: same-project heading re-ranking
|
|
2354
|
-
"
|
|
2355
|
-
"
|
|
2550
|
+
"HEADINGS: same-project heading re-ranking drives the local Things app (keyboard " +
|
|
2551
|
+
"shortcuts on the heading row; children follow) and must be turned on the same way as " +
|
|
2552
|
+
"areas below. It covers the open headings only — a completed or canceled heading is not " +
|
|
2553
|
+
"shown in the project view, so it is stepped over and cannot be moved or used as an " +
|
|
2554
|
+
"anchor. AREAS: this drives the local Things app (sidebar drag) and " +
|
|
2356
2555
|
"must be turned on with `things config set ui-enabled true` plus dangerously_drive_gui; " +
|
|
2357
2556
|
"the areas' projects and to-dos are untouched. Ordering the Today, Inbox, or Someday " +
|
|
2358
2557
|
"lists, or a project's to-dos, must first be enabled once via `things config set " +
|
|
@@ -2390,7 +2589,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2390
2589
|
};
|
|
2391
2590
|
return moveResult(await getClient().write.reorderAny(request, writeOptions(args)));
|
|
2392
2591
|
}));
|
|
2393
|
-
|
|
2592
|
+
registerTool("undo", {
|
|
2394
2593
|
description: "Undo the last N changes, newest first (changes made directly in the Things app " +
|
|
2395
2594
|
"cannot be undone here). By default this undoes only changes made through THIS " +
|
|
2396
2595
|
"connection — this client's own writes; it will not touch the user's own edits, or " +
|
|
@@ -2453,7 +2652,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2453
2652
|
});
|
|
2454
2653
|
return jsonResult(items);
|
|
2455
2654
|
}));
|
|
2456
|
-
|
|
2655
|
+
registerTool("capabilities", {
|
|
2457
2656
|
description: "Support reference for every operation kind usable with run_operation and batch: " +
|
|
2458
2657
|
"whether it is available, its caveats, and the confirmation parameters it needs.",
|
|
2459
2658
|
inputSchema: {
|
|
@@ -2464,7 +2663,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2464
2663
|
},
|
|
2465
2664
|
annotations: READ_ONLY,
|
|
2466
2665
|
}, async (args) => guard(() => jsonResult(capabilitiesTable(args.op))));
|
|
2467
|
-
|
|
2666
|
+
registerTool("doctor", {
|
|
2468
2667
|
description: "Check the environment: whether the Things app and its database are reachable, " +
|
|
2469
2668
|
"whether changes can be made, any one-time setup still needed (macOS permissions, " +
|
|
2470
2669
|
"the app's 'Enable Things URLs' setting), whether the environment changed since " +
|
|
@@ -2488,7 +2687,7 @@ export function createThingsMcpServer(options = {}) {
|
|
|
2488
2687
|
? jsonResult(report)
|
|
2489
2688
|
: errorResult(error ?? { code: "unexpected", message: "no report" });
|
|
2490
2689
|
}));
|
|
2491
|
-
|
|
2690
|
+
registerTool("op_result", {
|
|
2492
2691
|
description: "Look up what happened to a write you dispatched with op_id, from the local change history " +
|
|
2493
2692
|
"alone (opens no database, changes nothing). Use it to recover the outcome when the write " +
|
|
2494
2693
|
"call was interrupted before it returned: report FOUND (the final result + target + " +
|