@torsday/omnifocus-mcp 1.3.0 → 2.0.0

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.
Files changed (4) hide show
  1. package/CHANGELOG.md +347 -22
  2. package/README.md +31 -728
  3. package/dist/index.js +2525 -677
  4. package/package.json +27 -14
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  [![Platform: macOS 13+](https://img.shields.io/badge/platform-macOS%2013%2B-lightgrey)](https://www.apple.com/macos/)
8
8
  [![Mutation tested: Stryker](https://img.shields.io/badge/mutation--tested-stryker-orange)](./docs/adr/0017-mutation-testing-release-gate.md)
9
9
 
10
- > **Give any MCP-compatible AI assistant full, typed access to your OmniFocus.** Read your inbox, create tasks, close projects, batch-update dozens of items, evaluate perspectives, trigger sync — all through natural language. `omnifocus-mcp` wires an 80-tool MCP server directly to OmniFocus on macOS via JXA and OmniJS, with circuit breakers, rate limits, and an agent-aware error hierarchy so the assistant knows exactly what to do next when something goes wrong.
10
+ > **Give any MCP-compatible AI assistant full, typed access to your OmniFocus.** Read your inbox, create tasks, close projects, batch-update dozens of items, evaluate perspectives, trigger sync — all through natural language. `omnifocus-mcp` wires a 143-tool MCP server directly to OmniFocus on macOS via JXA and OmniJS, with circuit breakers, rate limits, and an agent-aware error hierarchy so the assistant knows exactly what to do next when something goes wrong.
11
11
 
12
12
  ---
13
13
 
@@ -17,19 +17,9 @@
17
17
  - [Why this exists](#why-this-exists)
18
18
  - [Quick start](#quick-start)
19
19
  - [Security & trust](#security--trust)
20
- - [Example interactions](#example-interactions)
21
- - [Prompts](#prompts)
22
- - [If you are an AI agent](#if-you-are-an-ai-agent)
23
- - [Tools](#tools)
24
- - [Resources](#resources)
25
- - [Transport text DSL](#transport-text-dsl)
26
20
  - [Architecture at a glance](#architecture-at-a-glance)
27
21
  - [Status and roadmap](#status-and-roadmap)
28
- - [Install](#install)
29
- - [Environment variables](#environment-variables)
30
- - [Troubleshooting](#troubleshooting)
31
- - [Client setup guides](#client-setup-guides)
32
- - [Design documents](#design-documents)
22
+ - [Reference docs](#reference-docs)
33
23
  - [Contributing](#contributing)
34
24
  - [License](#license)
35
25
 
@@ -68,10 +58,14 @@ OmniFocus is a powerful GTD tool, but it's an island. Your tasks sit there while
68
58
 
69
59
  The server is built to a single-user local-first standard: no network surface, no cloud sync, typed errors with agent-readable remediation hints, safe by default.
70
60
 
61
+ See [`docs/examples.md`](./docs/examples.md) for concrete prompt-to-tool-call sequences and [`docs/prompts.md`](./docs/prompts.md) for the bundled MCP prompt templates (`daily-review`, `weekly-review`, `capture-meeting`, `project-planning`).
62
+
71
63
  ---
72
64
 
73
65
  ## Quick start
74
66
 
67
+ **Prerequisites:** macOS 13 (Ventura) or later · OmniFocus 3.x or 4.x (4.x recommended; some tools require 4.x — see [version compatibility](./docs/troubleshooting.md#omnifocus-version-compatibility)) · Node 24+ (not required for Homebrew install)
68
+
75
69
  1. **Install**
76
70
  ```bash
77
71
  # Homebrew (no Node required)
@@ -89,7 +83,7 @@ The server is built to a single-user local-first standard: no network surface, n
89
83
  env: OMNIFOCUS_LOG_LEVEL=info # optional; "debug" is verbose
90
84
  ```
91
85
 
92
- Find your client below. Order is alphabetical; no client is recommended over another.
86
+ Two common cases inline; full per-client guides at [`docs/clients/`](./docs/clients/) (Claude Code, Claude Desktop, Codex, OpenCode, Pi, generic stdio).
93
87
 
94
88
  <details>
95
89
  <summary><strong>Claude Code</strong> (CLI; no file edit)</summary>
@@ -97,8 +91,7 @@ The server is built to a single-user local-first standard: no network surface, n
97
91
  ```bash
98
92
  claude mcp add omnifocus omnifocus-mcp
99
93
  ```
100
-
101
- Detailed guide: [`docs/clients/claude-code.md`](./docs/clients/claude-code.md)
94
+ Detailed: [`docs/clients/claude-code.md`](./docs/clients/claude-code.md)
102
95
  </details>
103
96
 
104
97
  <details>
@@ -115,98 +108,14 @@ The server is built to a single-user local-first standard: no network surface, n
115
108
  }
116
109
  }
117
110
  ```
118
-
119
- Detailed guide: [`docs/clients/claude-desktop.md`](./docs/clients/claude-desktop.md)
120
- </details>
121
-
122
- <details>
123
- <summary><strong>Cline</strong> (VS Code extension) — extension settings (JSON)</summary>
124
-
125
- In VS Code, open the Cline extension's MCP settings panel and add:
126
-
127
- ```json
128
- {
129
- "mcpServers": {
130
- "omnifocus": {
131
- "command": "omnifocus-mcp",
132
- "args": [],
133
- "env": { "OMNIFOCUS_LOG_LEVEL": "info" }
134
- }
135
- }
136
- }
137
- ```
138
-
139
- Cline's settings UI may surface the same fields as labelled inputs rather than raw JSON; the values are identical. Verify the panel location against the current Cline docs.
140
- </details>
141
-
142
- <details>
143
- <summary><strong>OpenAI Codex CLI</strong> — <code>~/.codex/config.toml</code> (TOML)</summary>
144
-
145
- ```toml
146
- [mcp_servers.omnifocus]
147
- command = "omnifocus-mcp"
148
- args = []
149
- env = { OMNIFOCUS_LOG_LEVEL = "info" }
150
- ```
151
-
152
- Detailed guide: [`docs/clients/codex.md`](./docs/clients/codex.md)
153
- </details>
154
-
155
- <details>
156
- <summary><strong>Cursor</strong> — <code>~/.cursor/mcp.json</code> (JSON)</summary>
157
-
158
- ```json
159
- {
160
- "mcpServers": {
161
- "omnifocus": {
162
- "command": "omnifocus-mcp",
163
- "args": [],
164
- "env": { "OMNIFOCUS_LOG_LEVEL": "info" }
165
- }
166
- }
167
- }
168
- ```
169
-
170
- Verify the path against the current Cursor docs — recent versions also accept project-scoped `.cursor/mcp.json` at the repo root.
171
- </details>
172
-
173
- <details>
174
- <summary><strong>Windsurf</strong> — <code>~/.codeium/windsurf/mcp_config.json</code> (JSON)</summary>
175
-
176
- ```json
177
- {
178
- "mcpServers": {
179
- "omnifocus": {
180
- "command": "omnifocus-mcp",
181
- "args": [],
182
- "env": { "OMNIFOCUS_LOG_LEVEL": "info" }
183
- }
184
- }
185
- }
186
- ```
187
-
188
- Verify the path against the current Windsurf docs — Codeium occasionally relocates config under `~/.codeium/`.
189
- </details>
190
-
191
- <details>
192
- <summary><strong>Generic stdio client</strong> (anything else that speaks MCP/stdio)</summary>
193
-
194
- Use your client's MCP config form. The shape is:
195
-
196
- ```text
197
- command: "omnifocus-mcp"
198
- args: []
199
- env: { "OMNIFOCUS_LOG_LEVEL": "info" }
200
- ```
201
-
202
- Detailed guide: [`docs/clients/generic-stdio.md`](./docs/clients/generic-stdio.md)
111
+ Detailed: [`docs/clients/claude-desktop.md`](./docs/clients/claude-desktop.md)
203
112
  </details>
204
113
 
205
114
  3. **Grant macOS Automation permission** on first use — the app running the MCP server will prompt to control OmniFocus; click **OK**. If denied by mistake: **System Settings → Privacy & Security → Automation → [app] → OmniFocus** ✓
206
115
 
207
116
  4. **Verify** — ask your assistant: *"Use the internal_status tool and tell me what it returns."*
208
117
 
209
- Detailed per-client guides: [`docs/clients/`](./docs/clients/)
118
+ Stuck? See [`docs/troubleshooting.md`](./docs/troubleshooting.md).
210
119
 
211
120
  ---
212
121
 
@@ -214,8 +123,6 @@ Detailed per-client guides: [`docs/clients/`](./docs/clients/)
214
123
 
215
124
  `omnifocus-mcp` is a **local-only** Node.js process that drives a **local** OmniFocus app via Apple's `osascript` runtime. Installing this package does not introduce cloud connectivity, telemetry, or network egress that wasn't already on your machine.
216
125
 
217
- ### Data flow
218
-
219
126
  ```
220
127
  OmniFocus DB (local) ─→ JXA / OmniJS via osascript (local) ─→ MCP server (local stdio)
221
128
 
@@ -272,443 +179,7 @@ Three recipes that take seconds; you don't have to take this README's word for a
272
179
 
273
180
  The threat model deliberately excludes anything outside this codebase: vulnerabilities in OmniFocus itself, Apple's JXA / OmniJS / `osascript` runtimes, transitive npm-dependency CVEs (track and patch via `npm audit` / Dependabot, but not part of this project's guarantees), and any attacker with root-equivalent local access (who could replace `osascript`, the MCP server binary, or your shell). See [SECURITY.md § Scope](./SECURITY.md#scope).
274
181
 
275
- ### Reference docs
276
-
277
- - [`SECURITY.md`](./SECURITY.md) — vulnerability reporting, scope
278
- - [`DESIGN.md` § 18 Security posture](./DESIGN.md#18-security-posture) — full threat model
279
- - [ADR-0004](./docs/adr/0004-raw-script-escape-hatch.md) — raw-script gating decision
280
-
281
- ---
282
-
283
- ## Example interactions
284
-
285
- **"What's in my inbox right now?"**
286
-
287
- The assistant calls `task_list` with `{ "available": true, "limit": 20 }` and returns a formatted list of actionable inbox tasks with their IDs, due dates, and flags.
288
-
289
- ---
290
-
291
- **"Create a task to 'review Q2 budget' due Friday, flagged, in the Finance project."**
292
-
293
- 1. Calls `project_list` to find the Finance project ID.
294
- 2. Calls `task_create` with `{ "name": "review Q2 budget", "projectId": "<id>", "dueDate": "end-of-week", "flagged": true }`.
295
- 3. Returns the created task with its persistent ID and confirms the due date resolved to the correct Friday.
296
-
297
- ---
298
-
299
- **"Mark all my overdue tasks as deferred to tomorrow."**
300
-
301
- 1. Calls `task_list` with `{ "dueBefore": "today", "available": true }` to find overdue items.
302
- 2. Calls `task_batch_update` with `{ "deferDate": "tomorrow" }` for all of them in one atomic call.
303
- 3. Reports: *"Deferred 7 overdue tasks to tomorrow. Call sync_trigger if you want iCloud to update immediately."*
304
-
305
- ---
306
-
307
- **"Show me what's due this week in the Work perspective."**
308
-
309
- 1. Calls `perspective_list` to find the "Work" perspective ID.
310
- 2. Calls `perspective_evaluate` with `{ "perspectiveId": "<id>" }` to get tasks in that perspective.
311
- 3. Filters and presents items with due dates within the current week.
312
-
313
- ---
314
-
315
- **"I just finished the sprint — complete all tasks in the Mobile App project."**
316
-
317
- 1. Calls `project_get` to retrieve the project and its tasks.
318
- 2. Calls `task_batch_complete` with the full list of task IDs in one call.
319
- 3. Confirms the count and suggests calling `sync_trigger` for cross-device visibility.
320
-
321
- ---
322
-
323
- ## Prompts
324
-
325
- `omnifocus-mcp` ships four **MCP prompt templates** — structured workflows you can invoke by name from any MCP client that surfaces `prompts/list` (most clients with a prompt picker UI).
326
-
327
- ### `daily-review` — triage your day
328
-
329
- Loads your snapshot, overdue tasks, and today's forecast; reschedules or drops overdue items; confirms due-today tasks; processes the inbox. No parameters needed.
330
-
331
- ```
332
- Use the daily-review prompt
333
- ```
334
-
335
- ### `weekly-review` — walk your projects
336
-
337
- Loads every project whose review date has arrived; checks each one for stale tasks; marks it reviewed or completes/drops it. No parameters needed.
338
-
339
- ```
340
- Use the weekly-review prompt
341
- ```
342
-
343
- ### `capture-meeting` — extract action items
344
-
345
- Takes raw meeting notes and creates OmniFocus tasks for every commitment, follow-up, and decision point. Pass the notes as text and optionally a project ID.
346
-
347
- ```
348
- Use the capture-meeting prompt with notes="Sync with Alice: she'll send the report by Thursday.
349
- Bob to review the contract. Need to schedule follow-up call."
350
- ```
351
-
352
- Results in two inbox tasks: "Send report to [person]" and "Review contract" with the source sentences as notes.
353
-
354
- ### `project-planning` — decompose a brief
355
-
356
- Creates a new project and populates it with a set of concrete, ordered, one-day tasks derived from a free-text brief.
357
-
358
- ```
359
- Use the project-planning prompt with name="Q3 Marketing Site" brief="Redesign the marketing
360
- site landing page and pricing page. New brand colors, updated copy, responsive mobile layout.
361
- Launch by end of July."
362
- ```
363
-
364
- Results in a new OmniFocus project with 8–12 tasks covering design, copy, development, and review phases, ready to schedule and assign.
365
-
366
- ---
367
-
368
- ## If you are an AI agent
369
-
370
- This section is written for you. It covers the conventions you need to use this MCP effectively without trial and error.
371
-
372
- ### IDs, not names
373
-
374
- Every OmniFocus resource — tasks, projects, tags, folders — is identified by a **persistent opaque ID** (e.g. `"hKx9vLmNp2"`). Names collide and change; IDs don't. Always resolve names to IDs with the corresponding `*_list` tool before calling any other tool.
375
-
376
- ### Error codes and what to do next
377
-
378
- Every error carries a stable `code`, a human-readable `suggestion`, and a machine-readable `remediationClass`:
379
-
380
- | `remediationClass` | Meaning | Your action |
381
- |--------------------|---------|-------------|
382
- | `environment` | OmniFocus is not running, permissions denied, or a Pro/version feature is missing | Stop. Surface `suggestion` to the user; do not retry automatically |
383
- | `input` | Bad ID, invalid field value, schema violation, or loop detected | Fix the input using `details` for specifics; retry |
384
- | `transient` | Timeout, rate limit, queue full, or circuit open | Wait `details.retryAfterMs` ms, then retry once |
385
- | `infrastructure` | JXA or OmniJS script failed | Retry once; if still failing, surface to user |
386
- | `lifecycle` | Server is shutting down | Reconnect to a fresh server instance |
387
-
388
- `RateLimited` and `CircuitOpen` always include `details.retryAfterMs` (default `60000` ms). Do not poll faster than that.
389
-
390
- ### Dates
391
-
392
- All date inputs accept either **ISO-8601 with UTC offset** (`"2026-04-22T09:00:00-07:00"`) or a **relative shortcut**:
393
-
394
- `today` · `tomorrow` · `yesterday` · `this-week` · `next-week` · `end-of-week` · `end-of-month`
395
-
396
- Shortcuts resolve to midnight in the server's local timezone.
397
-
398
- ### Mutations and sync
399
-
400
- Every write tool returns the full updated domain object, not just an acknowledgement. The response `meta.syncPending` is `true` immediately after a write — OmniFocus has saved locally but not yet synced to iCloud. Call `sync_trigger` if cross-device visibility matters; otherwise sync happens automatically within a few minutes.
401
-
402
- ### Null consistency
403
-
404
- All optional scalar fields are **always present** in responses, set to `null` when unset. You can safely destructure without null-checks on field presence.
405
-
406
- ### Idempotency — safe retries
407
-
408
- `project_create`, `project_update`, and `project_delete` accept an optional `idempotency_key?: string`. If you supply one and the call succeeds, replaying the exact same key within 5 minutes returns the cached result with `meta.idempotentReplay: true` and skips the OmniFocus call. Use a deterministic key scoped to your session and intent (e.g. `"session-abc/create-project-finance"`).
409
-
410
- ### Dry-run — validate before committing
411
-
412
- `task_update` and `project_update` accept `dry_run?: boolean`. When `true`, input is fully validated and the would-be result is returned, but nothing is written to OmniFocus. `meta.dryRun: true` is set on the response.
413
-
414
- ### Additive tag edits — no read-modify-write needed
415
-
416
- `task_update` accepts `addTags`, `removeTags`, and `setFlagged` patch fields alongside the existing full-replacement `tagIds` field. Prefer these for incremental edits — they apply a diff atomically inside the write queue with no race against concurrent user edits.
417
-
418
- ### Conflict detection — optimistic concurrency
419
-
420
- `task_update` accepts `expectedModifiedAt`. If the task was modified since your read, the server returns `OF_CONFLICT` (`remediationClass: "input"`). Re-read with `task_get`, merge your changes, and retry with the fresh `modifiedAt`.
421
-
422
- ### Loop detection — don't get stuck
423
-
424
- If you call the same tool with identical arguments 5+ times in a 60-second window, the server appends `WARN_LOOP_DETECTED` to `meta.warnings`. At 10 repetitions it throws `OF_LOOP_DETECTED` (`remediationClass: "input"`). Act on the result of your previous call rather than repeating it.
425
-
426
- ### Capabilities pre-flight
427
-
428
- Read `omnifocus://capabilities` once at session start. It returns OF version, edition (Standard/Pro), transport availability, and feature flags (`customPerspectives`, `forecastTag`, `rawScriptTools`). Use it to skip Pro-gated tools rather than discovering unavailability via error.
429
-
430
- ### Rate limit state — self-throttle before hitting the wall
431
-
432
- Every response includes `meta.rateLimit?: { remaining: number; resetAt: string }`. Check this after each call. If `remaining < 10`, slow down. If `remaining === 0`, do not call before `meta.rateLimit.resetAt`. The default limit is 120 calls/min per tool.
433
-
434
- ### Structured warnings — act on `meta.warnings[].code`
435
-
436
- Non-fatal issues appear in `meta.warnings` as `{ code, message, suggestion?, details? }`. Switch on `code`, not `message`:
437
-
438
- | `code` | Means | Action |
439
- |---|---|---|
440
- | `WARN_IDS_NOT_FOUND` | Some IDs in a bulk call were not found | Check `details.missing` |
441
- | `WARN_RESULT_TRUNCATED` | Response hit size limit; more items exist | Follow pagination cursor |
442
- | `WARN_SYNC_PENDING` | Write saved locally; iCloud sync not yet triggered | Call `sync_trigger` if needed |
443
- | `WARN_LOOP_DETECTED` | Same tool+args called ≥5 times in 60s | Act on previous result before repeating |
444
-
445
- ### Incremental sync — `updatedSince`
446
-
447
- `task_list` accepts `updatedSince?: string` (ISO-8601 or relative shortcut). Use it to fetch only changed items after your initial load:
448
-
449
- ```jsonc
450
- // First call: full load
451
- { "available": true, "limit": 200 }
452
-
453
- // Subsequent calls: only changes
454
- { "available": true, "updatedSince": "2026-04-21T10:00:00-07:00", "limit": 200 }
455
- ```
456
-
457
- Note: deleted items cannot be surfaced via `updatedSince` — compare `meta.snapshot` counts if you need to detect deletions.
458
-
459
- ### Navigation hints — follow `_links`
460
-
461
- Every `Task` response includes `_links` with resource URIs for related objects:
462
-
463
- ```jsonc
464
- {
465
- "id": "hKx9vLmNp2",
466
- "_links": {
467
- "self": "omnifocus://task/hKx9vLmNp2",
468
- "project": "omnifocus://project/pXY3",
469
- "tags": ["omnifocus://tag/tABC"]
470
- }
471
- }
472
- ```
473
-
474
- Pass the ID fragment to `task_get`, `project_get`, etc. You never need to construct a URI manually.
475
-
476
- ### Response envelope
477
-
478
- All responses have this shape:
479
-
480
- ```jsonc
481
- // success
482
- { "data": { … }, "meta": { "correlationId": "…", "durationMs": 12, "cacheHit": false, "transport": "jxa", "syncPending": false } }
483
-
484
- // error
485
- { "error": { "code": "OF_NOT_FOUND", "remediationClass": "input", "message": "…", "suggestion": "…", "details": { … } }, "meta": { … } }
486
- ```
487
-
488
- ### Where to start
489
-
490
- - **Daily work**: `task_list` (inbox or today filter) → `task_create` / `task_update` / `task_complete`
491
- - **Projects**: `project_list` → `project_create` / `project_update`
492
- - **Finding things**: `task_search` (keyword + optional tag/project/date filters); `tag_list` for available tags
493
- - **Bulk ops**: `task_batch_create` / `task_batch_update` / `task_batch_complete` for up to 50 items atomically
494
- - **Sync**: `sync_trigger` after bulk mutations; `internal_status` to check server health
495
-
496
- ---
497
-
498
- ## Tools
499
-
500
- Tools are organized by domain — tasks, projects, tags, folders, perspectives, forecast, review, search, notes, attachments, sync, export, and observability. See [`docs/tools.md`](./docs/tools.md) for the full auto-generated reference with the live registered count, input schemas, example calls, and example responses.
501
-
502
- ### App lifecycle
503
- | Tool | Description |
504
- |---|---|
505
- | `app_launch` | Explicitly launch OmniFocus (idempotent) |
506
-
507
- ### Tasks
508
- | Tool | Description |
509
- |---|---|
510
- | `task_list` | List tasks with filters (available, flagged, due, project, tag, updatedSince) |
511
- | `task_get` | Get a single task by ID |
512
- | `task_get_many` | Get multiple tasks by ID in one call |
513
- | `task_create` | Create a task (project, tags, due, defer, flag, note, repeat) |
514
- | `task_update` | Update a task (addTags/removeTags, dry_run, expectedModifiedAt) |
515
- | `task_complete` | Mark a task complete |
516
- | `task_uncomplete` | Unmark a completed task |
517
- | `task_delete` | Delete a task |
518
- | `task_drop` | Drop (defer indefinitely) a task |
519
- | `task_undrop` | Restore a dropped task |
520
- | `task_move` | Reparent a task to a different project or parent task |
521
- | `task_reorder` | Reorder a task among its siblings |
522
- | `task_duplicate` | Duplicate a task (optionally recursive) |
523
- | `task_find_by_name` | Find tasks by exact or fuzzy name match |
524
- | `task_search` | Full-text search across task names and notes, with optional tag/project/date/availability filters |
525
- | `task_set_repetition` | Set a repeat rule on a task |
526
- | `task_clear_repetition` | Remove a repeat rule from a task |
527
- | `task_parse_transport_text` | Parse transport text DSL → structured tasks (no side effects) |
528
- | `task_batch_create` | Create up to 50 tasks atomically |
529
- | `task_batch_update` | Update up to 50 tasks atomically |
530
- | `task_batch_complete` | Complete up to 50 tasks atomically |
531
-
532
- ### Projects
533
- | Tool | Description |
534
- |---|---|
535
- | `project_list` | List projects with filters (folder, status) |
536
- | `project_get` | Get a single project by ID |
537
- | `project_create` | Create a project (idempotency_key supported) |
538
- | `project_update` | Update a project (dry_run, idempotency_key, expectedModifiedAt) |
539
- | `project_complete` | Mark a project complete |
540
- | `project_delete` | Delete a project (idempotency_key supported) |
541
- | `project_drop` | Drop (defer indefinitely) a project |
542
- | `project_move` | Move a project to a different folder |
543
- | `project_mark_reviewed` | Mark a project as reviewed (alias for review_mark_reviewed) |
544
-
545
- ### Folders
546
- | Tool | Description |
547
- |---|---|
548
- | `folder_list` | List folders |
549
- | `folder_get` | Get a single folder by ID |
550
- | `folder_create` | Create a folder |
551
- | `folder_update` | Rename a folder |
552
- | `folder_delete` | Delete a folder |
553
- | `folder_move` | Move a folder to a parent folder |
554
-
555
- ### Tags
556
- | Tool | Description |
557
- |---|---|
558
- | `tag_list` | List tags |
559
- | `tag_get` | Get a single tag by ID |
560
- | `tag_create` | Create a tag |
561
- | `tag_update` | Rename a tag |
562
- | `tag_delete` | Delete a tag |
563
- | `tag_move` | Move a tag under a parent tag |
564
- | `tag_set_status` | Set tag status (active/on-hold/dropped) |
565
- | `tag_set_allows_next_action` | Toggle "allows next action" on a tag |
566
- | `tag_get_location` | Get a tag's location in the hierarchy |
567
- | `tag_set_location` | Set a tag's location in the hierarchy |
568
-
569
- ### Notes
570
- | Tool | Description |
571
- |---|---|
572
- | `note_get` | Get a task or project note (plain text) |
573
- | `note_get_html` | Get a task or project note (HTML) |
574
- | `note_set` | Set a task or project note (plain text, replaces) |
575
- | `note_set_html` | Set a task or project note (HTML, replaces) |
576
- | `note_append` | Append text to a task or project note |
577
-
578
- ### Attachments
579
- | Tool | Description |
580
- |---|---|
581
- | `attachment_list` | List attachments on a task or project |
582
- | `attachment_add` | Embed a local file as an attachment |
583
- | `attachment_remove` | Remove an attachment by ID |
584
- | `attachment_save_to_path` | Save an attachment's bytes to a local file |
585
-
586
- ### Perspectives
587
- | Tool | Description |
588
- |---|---|
589
- | `perspective_list` | List all perspectives (built-in and custom) |
590
- | `perspective_evaluate` | Evaluate a perspective and return its tasks |
591
-
592
- ### Forecast & search
593
- | Tool | Description |
594
- |---|---|
595
- | `forecast_get` | Get today's forecast grouped by overdue / due today / due later / inbox |
596
- | `search_query` | Full-text search across tasks and projects |
597
-
598
- ### Review
599
- | Tool | Description |
600
- |---|---|
601
- | `review_list_due` | List projects whose next review date is today or past |
602
- | `review_mark_reviewed` | Mark a project as reviewed and set the next review date |
603
- | `review_set_interval` | Set the review interval (days) for a project |
604
-
605
- ### Sync & app
606
- | Tool | Description |
607
- |---|---|
608
- | `sync_trigger` | Trigger an OmniFocus iCloud sync |
609
- | `sync_status` | Get the last sync timestamp and status |
610
-
611
- ### Plug-ins
612
- | Tool | Description |
613
- |---|---|
614
- | `plugin_invoke` | Invoke an installed Omni Automation plug-in by bundle identifier |
615
-
616
- ### Export & import
617
- | Tool | Description |
618
- |---|---|
619
- | `export_opml` | Export a project (or all projects) as OPML |
620
- | `export_taskpaper` | Export a project (or all projects) as TaskPaper |
621
- | `import_opml` | Import tasks from an OPML string into OmniFocus |
622
- | `import_taskpaper` | Import tasks from a TaskPaper string into OmniFocus |
623
-
624
- ### Observability
625
- | Tool | Description |
626
- |---|---|
627
- | `internal_status` | Server health: transport status, queue depths, cache stats, rate limits |
628
-
629
- ### Raw scripts _(opt-in, off by default)_
630
- | Tool | Description |
631
- |---|---|
632
- | `run_jxa_script` | Execute arbitrary JXA — requires `OMNIFOCUS_ALLOW_RAW_SCRIPT=1` |
633
- | `run_omnijs_script` | Execute arbitrary OmniJS — requires `OMNIFOCUS_ALLOW_RAW_SCRIPT=1` |
634
-
635
- ---
636
-
637
- ## Resources
638
-
639
- The server registers resources under the `omnifocus://` scheme. Resources are read-only, URI-addressable, and enumerable via `resources/list`. Templated URIs follow [RFC 6570](https://www.rfc-editor.org/rfc/rfc6570) and accept the listed parameters as query strings or path segments.
640
-
641
- **Static URIs** — read with no parameters:
642
-
643
- | URI | Returns |
644
- |---|---|
645
- | `omnifocus://capabilities` | Server capabilities: OF version, edition, transport status, feature flags, calendar-bridge availability |
646
- | `omnifocus://snapshot` | Orientation counts: inbox, flagged, overdue, dueToday, reviewDue, syncStatus |
647
- | `omnifocus://inbox` | Inbox tasks as `Task[]` |
648
- | `omnifocus://tasks/inbox` | Inbox tasks (alias of `omnifocus://inbox`) |
649
- | `omnifocus://forecast/today` | Today's forecast grouped by overdue / dueToday / deferredToday / flagged |
650
- | `omnifocus://overdue` | All overdue tasks sorted by dueDate ASC |
651
- | `omnifocus://flagged` | All flagged available tasks |
652
- | `omnifocus://review-due` | Projects with nextReviewDate ≤ today |
653
- | `omnifocus://intents` | User-phrase → tool-sequence map: a small set of human-meaningful verbs that compose the full tool surface |
654
- | `omnifocus://stats` | Database-wide rollup: counts by project, tag, completion state |
655
- | `omnifocus://taxonomy-audit` | Structural audit — inconsistent tag/folder usage, orphans, drift signals |
656
- | `omnifocus://waiting-on` | Every task carrying a `waiting-on` fence, sorted by daysOverdue DESC |
657
-
658
- **Templated URIs** — accept parameters:
659
-
660
- | URI Template | Parameters | Returns |
661
- |---|---|---|
662
- | `omnifocus://project/{id}` | `id` | Single project + full task tree |
663
- | `omnifocus://tag/{id}` | `id` | Single tag + its active tasks |
664
- | `omnifocus://perspective/{id}` | `id` | Perspective evaluation result (same shape as `perspective_evaluate`); Pro only |
665
- | `omnifocus://tasks/project/{projectId}` | `projectId` | Active tasks under a project |
666
- | `omnifocus://tasks/tag/{tagId}` | `tagId` | Active tasks carrying a tag |
667
- | `omnifocus://recent-activity{?hours}` | `hours` (default: 24) | Tasks completed/dropped/created in the last N hours |
668
- | `omnifocus://retrospective{?from,to}` | `from`, `to` (ISO-8601) | Closed-task aggregation for a date range — weekly review fuel |
669
- | `omnifocus://velocity{?weeks}` | `weeks` (default: 4) | Per-week throughput: completed counts, completion rate trend |
670
- | `omnifocus://burndown/{projectId}` | `projectId` | Per-project burndown vs naive linear ideal; needs project dueDate |
671
- | `omnifocus://project-health{?staleDays}` | `staleDays` (default: 14) | Triage list: stalled projects, no-activity, review-overdue |
672
- | `omnifocus://calendar{?from,to}` | `from`, `to` (ISO-8601, defaults to today local) | macOS Calendar events from EventKit; needs Calendar TCC grant |
673
- | `omnifocus://agenda{?date}` | `date` (ISO-8601, defaults to today local) | Merged daily timeline: calendar events + OF forecast, kind-tagged |
674
-
675
- ---
676
-
677
- ## Transport text DSL
678
-
679
- `task_parse_transport_text` parses a lightweight DSL inspired by OmniFocus Mail Drop into structured task objects. **No tasks are created** — pass the returned `tasks[]` to `task_create` or `task_batch_create` separately.
680
-
681
- ### Token syntax
682
-
683
- | Token | Example | Meaning |
684
- |---|---|---|
685
- | `@tag` | `@work` | Assign a tag by name |
686
- | `#date` | `#2026-05-01` or `#today` | Due date |
687
- | `::date` | `::tomorrow` | Defer date |
688
- | `!!` | `!!` | Flag the task |
689
- | `//text` | `//Call back before noon` | Append as task note |
690
- | `Project: Name` | `Project: Finance` | Set project context for subsequent tasks |
691
-
692
- ### Date shortcuts
693
-
694
- `today` · `tomorrow` · `yesterday` — resolved to midnight local time.
695
-
696
- Full ISO-8601 dates (`YYYY-MM-DD`) are also accepted. Unparseable dates emit a `warnings[]` entry.
697
-
698
- ### Example
699
-
700
- ```
701
- Project: Work
702
- Prepare Q2 report @work #end-of-week !!
703
- Send draft to Alice @work @email #tomorrow //attach spreadsheet
704
- Follow up with Bob ::next-week
705
-
706
- Project: Personal
707
- Buy groceries @errands #today
708
- Call dentist @phone ::tomorrow !! //ask about X-ray appointment
709
- ```
710
-
711
- Tag names and project names are raw strings — resolve to IDs with `tag_list` and `project_list` before passing to `task_create`.
182
+ Full threat model: [`SECURITY.md`](./SECURITY.md), [`docs/design/security.md`](./docs/design/security.md).
712
183
 
713
184
  ---
714
185
 
@@ -739,7 +210,7 @@ flowchart LR
739
210
  - **30s LRU read cache** — invalidated on every write. Mutations are never served stale.
740
211
  - **Middleware stack** — every registered tool runs through: `assertNotShuttingDown` → `circuitBreaker` → `rateLimitMeta` → `loopDetection`.
741
212
 
742
- The full layered diagram with queues, circuit breakers, and the test adapter lives in [`DESIGN.md §6`](./DESIGN.md#6-architecture).
213
+ The full layered diagram with queues, circuit breakers, and the test adapter lives in [`docs/design/architecture.md`](./docs/design/architecture.md).
743
214
 
744
215
  ---
745
216
 
@@ -760,194 +231,26 @@ Track open issues and future enhancements on the [**GitHub Project board**](http
760
231
 
761
232
  ---
762
233
 
763
- ## Install
764
-
765
- **Homebrew** (recommended for non-Node users):
766
- ```bash
767
- brew install torsday/tap/omnifocus-mcp
768
- ```
769
-
770
- **npm** (recommended if you already have Node 24+):
771
- ```bash
772
- # Global install
773
- npm install -g @torsday/omnifocus-mcp
774
-
775
- # Or run without installing (npx)
776
- npx -y @torsday/omnifocus-mcp
777
- ```
778
-
779
- **Any stdio MCP client** — add to your client's MCP server config (exact key name varies by client):
780
- ```json
781
- {
782
- "mcpServers": {
783
- "omnifocus": {
784
- "command": "omnifocus-mcp",
785
- "args": [],
786
- "env": { "OMNIFOCUS_LOG_LEVEL": "info" }
787
- }
788
- }
789
- }
790
- ```
791
-
792
- **npx (no global install)**:
793
- ```json
794
- {
795
- "mcpServers": {
796
- "omnifocus": {
797
- "command": "npx",
798
- "args": ["-y", "@torsday/omnifocus-mcp"],
799
- "env": { "OMNIFOCUS_LOG_LEVEL": "info" }
800
- }
801
- }
802
- }
803
- ```
804
-
805
- On first run, macOS asks the app running the server for permission to automate OmniFocus. Click **OK**. If you denied it by mistake: **System Settings → Privacy & Security → Automation → [app] → OmniFocus** ✓
806
-
807
- See the [troubleshooting guide](./docs/troubleshooting.md) and per-client guides in [`docs/clients/`](./docs/clients/) for detailed setup.
808
-
809
- ---
810
-
811
- ## Environment variables
812
-
813
- | Variable | What | Default |
814
- |---|---|---|
815
- | `OMNIFOCUS_LOG_LEVEL` | `trace`\|`debug`\|`info`\|`warn`\|`error` — logs go to stderr | `info` |
816
- | `OMNIFOCUS_CACHE_TTL_MS` | Read-cache TTL in milliseconds | `30000` |
817
- | `OMNIFOCUS_READ_POOL_SIZE` | Concurrent `osascript` processes for reads | `2` |
818
- | `OMNIFOCUS_WRITE_QUEUE_CAP` | Max pending writes before `QueueFull` error | `50` |
819
- | `OMNIFOCUS_JXA_TIMEOUT_MS` | Per-call JXA hard timeout in milliseconds | `30000` |
820
- | `OMNIFOCUS_OMNIJS_TIMEOUT_MS` | Per-call OmniJS hard timeout in milliseconds | `45000` |
821
- | `OMNIFOCUS_ATTACHMENT_PATHS` | Colon-separated allowlist of absolute path prefixes for attachment ops | `$HOME` |
822
- | `OMNIFOCUS_MAX_ATTACHMENT_MB` | Maximum attachment file size in MB (0 = no cap) | `100` |
823
- | `OMNIFOCUS_TOOL_RATE_LIMIT` | Per-tool rate limit in `N/SECONDS` format | `120/60` |
824
- | `OMNIFOCUS_ALLOW_RAW_SCRIPT` | Set to `1` to register `run_jxa_script` / `run_omnijs_script` | unset |
825
- | `OMNIFOCUS_INTEGRATION` | Set to `1` to enable the integration test suite | unset |
826
-
827
- Full table with override semantics: [`DESIGN.md §22`](./DESIGN.md#22-configuration--environment).
828
-
829
- ### Running integration tests
830
-
831
- Integration tests run against a live OmniFocus install:
832
-
833
- ```bash
834
- # 1. Make sure OmniFocus is running and Automation permission is granted
835
- # 2. Seed fixture data (idempotent — safe to re-run):
836
- node scripts/seed-integration-db.js
837
-
838
- # Optional: wipe and re-create all fixtures from scratch:
839
- node scripts/seed-integration-db.js --clean
840
-
841
- # 3. Run the integration suite:
842
- OMNIFOCUS_INTEGRATION=1 pnpm test:integration
843
- ```
844
-
845
- The seed script creates `mcp-fixture:` prefixed items (folders, projects, tasks, tags) that integration tests rely on.
846
-
847
- ---
848
-
849
- ## Troubleshooting
850
-
851
- ### OmniFocus is not running
852
-
853
- **Error:** `OF_NOT_RUNNING` — OmniFocus must be open for most operations.
854
-
855
- **Fix:** Launch OmniFocus manually, or call `app_launch` to open it via MCP.
856
-
857
- ---
858
-
859
- ### macOS Automation permission denied
860
-
861
- **Symptom:** Every tool call returns `OF_PERMISSION_DENIED`.
862
-
863
- **Fix:**
864
- 1. Open **System Settings → Privacy & Security → Automation**.
865
- 2. Find the app running the MCP server (Terminal, your AI client app, or your CI runner's shell).
866
- 3. Enable the **OmniFocus** checkbox.
867
- 4. Restart omnifocus-mcp.
868
-
869
- ```bash
870
- bash scripts/check-automation-permission.sh
871
- ```
872
-
873
- ---
874
-
875
- ### First-call timeout / slow startup
876
-
877
- JXA starts an `osascript` subprocess on each call. The first call after a system sleep or a fresh OmniFocus launch can take 5–15 seconds while the database loads. This is normal.
878
-
879
- If calls consistently time out:
880
- ```bash
881
- OMNIFOCUS_JXA_TIMEOUT_MS=60000 omnifocus-mcp
882
- ```
883
-
884
- ---
885
-
886
- ### `run_jxa_script` / `run_omnijs_script` not available
887
-
888
- **Error:** `ValidationError: run_jxa_script is not available in this adapter configuration`
889
-
890
- **Fix:** The raw-script tools are opt-in. Start the server with:
891
- ```bash
892
- OMNIFOCUS_ALLOW_RAW_SCRIPT=1 omnifocus-mcp
893
- ```
894
-
895
- See [`docs/adr/0004-raw-script-escape-hatch.md`](./docs/adr/0004-raw-script-escape-hatch.md) for the security rationale.
896
-
897
- ---
898
-
899
- ### Stale data after a write
900
-
901
- Writes are saved locally and show up immediately in subsequent tool calls. Changes don't reach other devices until iCloud sync runs. Call `sync_trigger` after bulk mutations or when cross-device visibility matters.
902
-
903
- ---
904
-
905
- ### More help
906
-
907
- - [`docs/troubleshooting.md`](./docs/troubleshooting.md) — expanded troubleshooting guide
908
- - [`docs/clients/`](./docs/clients/) — per-client setup guides
909
-
910
- ---
911
-
912
- ## Client setup guides
913
-
914
- | Client | Guide |
915
- |---|---|
916
- | Claude Code (CLI) | [`docs/clients/claude-code.md`](./docs/clients/claude-code.md) |
917
- | Claude Desktop | [`docs/clients/claude-desktop.md`](./docs/clients/claude-desktop.md) |
918
- | OpenAI Codex CLI | [`docs/clients/codex.md`](./docs/clients/codex.md) |
919
- | Generic stdio client | [`docs/clients/generic-stdio.md`](./docs/clients/generic-stdio.md) |
920
-
921
- ---
922
-
923
- ## Design documents
924
-
925
- - **[`SPEC.md`](./SPEC.md)** — functional scope and non-functional requirements; resolved v1 decisions
926
- - **[`DESIGN.md`](./DESIGN.md)** — 28-section architecture; options evaluated; R/S/M assessment; example tool implementation
927
- - **[`docs/security.md`](./docs/security.md)** — attack surface, mitigations, and test coverage
928
- - **[`docs/domain-reference.md`](./docs/domain-reference.md)** — OmniFocus glossary, canonical schemas, lossiness matrix for export/import
929
- - **[`docs/adr/`](./docs/adr/)** — Architecture Decision Records covering every load-bearing choice:
234
+ ## Reference docs
930
235
 
931
- | # | Decision |
236
+ | Doc | What |
932
237
  |---|---|
933
- | [0001](./docs/adr/0001-language-and-runtime.md) | TypeScript on Node.js 24 |
934
- | [0002](./docs/adr/0002-omnifocus-transport-dual.md) | JXA + OmniJS dual transport |
935
- | [0003](./docs/adr/0003-tool-surface-namespaced.md) | `<noun>_<verb>` tool namespacing |
936
- | [0004](./docs/adr/0004-raw-script-escape-hatch.md) | Opt-in raw-script tools |
937
- | [0005](./docs/adr/0005-script-assets-as-files.md) | Scripts as first-class files |
938
- | [0006](./docs/adr/0006-read-cache-strategy.md) | 30s LRU, invalidate-on-write |
939
- | [0007](./docs/adr/0007-dates-iso8601-with-offset.md) | ISO-8601 with offset at the boundary |
940
- | [0008](./docs/adr/0008-ids-branded-opaque-strings.md) | Branded opaque ID types |
941
- | [0009](./docs/adr/0009-concurrency-pool-and-queue.md) | Read pool + write queue + OmniJS queue |
942
- | [0010](./docs/adr/0010-mcp-transport-stdio.md) | stdio-only MCP transport (v1) |
943
- | [0011](./docs/adr/0011-versioning-and-stability.md) | Semver with explicit contract |
944
- | [0012](./docs/adr/0012-distribution-npx.md) | Distribution via `npx` / npm |
945
- | [0013](./docs/adr/0013-tool-response-envelope.md) | Uniform response envelope |
946
- | [0014](./docs/adr/0014-e2e-harness-strategy.md) | In-memory adapter switch for E2E |
947
- | [0015](./docs/adr/0015-nl-excellence-response-envelope.md) | NL-excellence envelope: clarification, hints, summary |
948
- | [0016](./docs/adr/0016-webhook-delivery.md) | Webhook delivery for OmniFocus state changes |
949
- | [0017](./docs/adr/0017-mutation-testing-release-gate.md) | Stryker mutation testing as release-time hard gate |
950
- | [0018](./docs/adr/0018-calendar-bridge-eventkit-only.md) | Calendar bridge — EventKit-only via Swift-binary subprocess |
238
+ | [`docs/tools.md`](./docs/tools.md) | Auto-generated reference for every tool — input schemas, examples, responses |
239
+ | [`src/tools/INDEX.md`](./src/tools/INDEX.md) | One-line-per-tool index grouped by domain (cheaper than grepping) |
240
+ | [`docs/examples.md`](./docs/examples.md) | Concrete prompt → tool-call sequences |
241
+ | [`docs/prompts.md`](./docs/prompts.md) | Bundled MCP prompt templates (`daily-review`, `weekly-review`, `capture-meeting`, `project-planning`) |
242
+ | [`AGENTS.md`](./AGENTS.md) | Agent-facing guide engineering conventions for contributors AND calling conventions for clients (IDs, error codes, dates, idempotency, `_links`, response envelope, `meta.warnings`, rate limits) |
243
+ | [`docs/clients/`](./docs/clients/) | Per-client setup guides (Claude Code, Claude Desktop, Codex, OpenCode, Pi, generic stdio) |
244
+ | [`docs/troubleshooting.md`](./docs/troubleshooting.md) | OmniFocus not running, Automation permission, slow startup, raw-script gating, sync staleness |
245
+ | [`docs/domain-reference.md`](./docs/domain-reference.md) | OmniFocus glossary, canonical schemas, lossiness matrix for export/import |
246
+ | [`docs/security.md`](./docs/security.md) | Attack surface, mitigations, test coverage |
247
+ | [`SECURITY.md`](./SECURITY.md) | Vulnerability reporting, scope |
248
+ | [`SPEC.md`](./SPEC.md) | Functional scope and resolved v1 decisions |
249
+ | [`DESIGN.md`](./DESIGN.md) | Index of the per-area design files under [`docs/design/`](./docs/design/) — architecture, envelope, IDs/dates, security, testing, observability, configuration, distribution, example tool, resources |
250
+ | [`docs/adr/`](./docs/adr/) | Architecture Decision Records — every load-bearing choice (TypeScript+Node 24, dual transport, namespacing, raw-script gating, scripts-as-files, LRU cache, ISO-8601 dates, branded IDs, pool+queue, stdio transport, semver, npx distribution, response envelope, E2E adapter switch, NL envelope, webhooks, Stryker mutation gate, EventKit calendar bridge, cross-transport ID interop, JXA helper inlining, reactive runtime spike, envelope text/structured split, runner-host JXA bridge contention) |
251
+ | [`CHANGELOG.md`](./CHANGELOG.md) | Release history per [Keep a Changelog](https://keepachangelog.com/) |
252
+
253
+ For the **full environment-variable surface** with override semantics see [`docs/design/configuration.md`](./docs/design/configuration.md); the load-bearing knobs are `OMNIFOCUS_LOG_LEVEL`, `OMNIFOCUS_CACHE_TTL_MS`, `OMNIFOCUS_ALLOW_RAW_SCRIPT`, and `OMNIFOCUS_ATTACHMENT_PATHS`.
951
254
 
952
255
  ---
953
256
 
@@ -959,4 +262,4 @@ This is a single-developer project; external contributions are not currently sol
959
262
 
960
263
  ## License
961
264
 
962
- [MIT](./LICENSE) — see full text in `LICENSE`.
265
+ MIT — see [`LICENSE`](./LICENSE).