Skip to content

Releases

v1.17.0

Choose a tag to compare

@themotionmachine themotionmachine released this 26 Sep 19:46

Fixed repeats count from the right date

Every fixed repeat written through this server was anchored on the due date (#140). The AppleScript form used to write repeats has no anchor field, and OmniFocus 4.7+ fills it with DueDate, whatever dates the item has. A due-anchored repeat on an item with no due date grows a due date when completed.

  • repeat / newRepeat now take anchor (defer | due | planned) and catchUp, both for fixed only.
  • Omitted, a fixed repeat anchors on the due date if the item has one, otherwise the defer date. This changes behavior only for items with no due date, which is exactly the broken case.
  • The stored rule is read back and checked, and edit_item reports the anchor it stored.
  • query_omnifocus gains repetitionAnchor, repetitionSchedule and catchUpAutomatically. repetitionMethod: "Fixed" alone couldn't tell the anchors apart.
  • Setting a repeat now requires OmniFocus 4.7 or later.

New in edit_item

  • Nest and order existing tasks (#138): newParentTaskId ("" = back to the top level) and position ("beginning", "end", {before: id}, {after: id}). Tasks keep their ids, so links to them survive.
  • Project review intervals (#139): newReviewInterval: {steps, unit}.

Query fixes — several of these change results

  • Security: sortBy could inject code (#142). It was spliced unvalidated into the script OmniFocus runs. It's now a fixed set of keys. modificationDate, creationDate and taskStatus (now ranked by urgency) sort for the first time; before, they silently did nothing.
  • dueWithin: "today" missed tasks due later today (#144). Forward *Within filters cut off at the current time of day. They now run to the end of the day, and still include overdue items.
  • Filters that don't apply to the queried entity are rejected (#146) instead of being silently ignored, which used to return the unfiltered set as the answer. status values are checked per entity. Backward-looking *Within filters reject future values.
  • Newly implemented: isRepeating on projects, and folderId, folderName and status on folders.
  • reviewInterval read back as null for every project (#145). It now renders as "2 weeks", "1 month" and so on.
  • limit must be a non-negative integer (#142).

If you have saved queries: a query that passed a filter to the wrong entity, a status value from another entity, a future value to a backward-looking filter, or an unknown sortBy now returns an error naming the problem. Before, it silently returned a wrong answer.

Also

  • list_tags renders nesting at any depth (#136). Tags two or more levels deep used to print as root tags, which looked like a stale cache.
  • QUERY_TOOL_REFERENCE.md has been rewritten from the code, with a test that fails on undocumented filters (#137).

Full changelog: v1.16.0...v1.17.0

v1.16.0

Choose a tag to compare

@themotionmachine themotionmachine released this 18 Sep 20:48

Silent failures now fail loudly

  • edit_item refuses a no-op (#132). A call with a target but no editable field — typically a misspelled field like note for newNote — used to answer "updated successfully" for a write that never happened. It now errors and lists the accepted fields.
  • Unknown argument keys are rejected on every tool, at any depth (#132, #133). Zod's non-strict objects silently stripped them; filters: {inInbox: true} (the filter is inbox) ran unfiltered and reported the whole database as the answer. Now: Unrecognized key(s) in object: 'inInbox'.
  • query_omnifocus validates status values and fields names and accepts the same date union (number | 'today' | 'YYYY-MM-DD' | …) on the past-looking filters (addedWithin, completedOn, …) as on the future-looking ones. Folder status now maps through Folder.Status rather than Project.Status, so it stops coming back undefined. (#128 → #134, thanks @rjames86)

Long-lived clients keep their tools

  • The idle backstop pings before it exits (#127, closes #126, thanks @tarekrached). After OMNIFOCUS_MCP_IDLE_TIMEOUT_MINUTES of silence the shim (and the standalone server) now send a JSON-RPC ping; a client that answers keeps its session. Only an unanswered ping ends it. Claude Code sessions no longer show the server as failed after 30 quiet minutes. The SIGKILL-orphan protection from #86 survives unchanged.

Perspectives and listings match the app

  • Perspective rule engine fixes (#129, thanks @rjames86). Five evaluators referenced OmniJS properties that don't exist (tag.effectivelyDropped, project.effectivelyCompleted, Project.Status.SingleActions, Project.Status.Stalled/.Pending, droppedDate), so those rules matched everything or nothing. Fixed against the real API; stalled/pending are computed per OmniGroup's glossary; actionDateIsInTheNext and the hour component are now implemented. Tasks in a completed project no longer leak through "Availability: remaining".
  • Container-dropped items are excluded (#130, thanks @rjames86). Projects and folders under a dropped folder, and tags under a dropped/on-hold parent, no longer appear in default listings (includeCompleted still surfaces them).

Housekeeping

  • #135 removed a node_modules symlink that briefly landed on main in the #134 merge. Never in a published package.

Full changelog: v1.15.0...v1.16.0

v1.15.0

Choose a tag to compare

@themotionmachine themotionmachine released this 26 Aug 01:35

What's Changed

Repeating items are no longer mistaken for duplicates (#124)

OmniFocus keeps every completed occurrence of a repeating item as its own row, carrying the same name and the same repetition rule as the live one. A query with includeCompleted: true therefore returned rows that were indistinguishable from duplicates — and mutating one cascades through the live repeat chain. In one real incident, eight targeted changes produced roughly fifteen dropped rows, including a future occurrence of a daily task.

  • query_omnifocus now returns isPastOccurrence (included by default) and labels those rows in its output: ⟲ past occurrence — not a duplicate.
  • edit_item and remove_item refuse to mutate a past occurrence, explaining why. Pass allowPastOccurrence: true for the rare case where you genuinely mean the historical row.

Completing or editing the live occurrence is unaffected — that is the normal way repeats work and it continues to behave exactly as before. Note the check is on repeating item plus terminal status, not on the shape of the id: a repeating project's live identifier is itself dotted (abc.116 can be the active project while abc.116.43 is history), so rejecting dotted ids would have made repeating projects uneditable.

Clients survive a daemon restart (#123)

If the shared daemon died while clients were attached — a crash, or a manual kill during troubleshooting — every attached client lost its OmniFocus tools for the rest of its session. The shim exited on the assumption the client would relaunch; clients that treat a stdio server exit as terminal do not. One long-running session lost the tools for a week without noticing.

The shim now reconnects, replays the client's initialize handshake so the new daemon's session matches what the client still believes, and returns retryable errors for any requests that were in flight when the connection dropped rather than leaving the client waiting on responses that no longer exist. Bounded to five reconnects, after which it exits as before.

Note that a write interrupted mid-flight may have partially applied — re-query before retrying a write, rather than assuming it did not land.

Full Changelog: v1.14.1...v1.15.0

v1.14.1

Choose a tag to compare

@themotionmachine themotionmachine released this 19 Aug 14:53

Fixes

  • Retries no longer deepen OmniFocus AppleEvent wedges (#121). When OmniFocus stopped responding, a read would replay its full query up to three times — 500ms and 1500ms apart — into an app that had already failed to answer for 60 seconds. A process sample of a wedged app showed the cost: eight permanently blocked AppleEvent dispatch queues, one for every osascript the server killed. Nothing on the client side can release them, so each retry made recovery strictly harder.

    The server now distinguishes the two failure signals it had been treating as one: a -1712 means OmniFocus answered and is merely contended (still retried, with longer 2s/6s backoffs), while a timeout kill means it never answered at all (never retried). When the app is unresponsive, a circuit breaker fails fast with a clear OmniFocus is not responding error instead of dispatching more work, and a single cheap liveness probe after a 30-second cooldown decides when to resume.

    If you do hit an unresponsive app, the error now names the recovery that actually works: quit every omnifocus-mcp process, then restart OmniFocus — in that order, because a client still mid-query will re-wedge a freshly launched app.

    New environment variables: OMNIFOCUS_MCP_UNRESPONSIVE_COOLDOWN_MS (default 30000) and OMNIFOCUS_MCP_PROBE_TIMEOUT_MS (default 5000).

Full Changelog: v1.14.0...v1.14.1

v1.14.0

Choose a tag to compare

@themotionmachine themotionmachine released this 18 Aug 18:37

What's Changed

Features

  • Repeating tasks and projects can now be created and edited (#116). Previously no write tool could set a repetition rule, so every recurring item an assistant created arrived inert and needed a manual trip to the OmniFocus inspector. add_omnifocus_task, add_project, and each batch_add_items item now accept a repeat object; edit_item accepts newRepeat (pass null to clear). You describe the schedule and the server compiles the ICS recurrence rule:

    { "method": "start-after-completion", "unit": "week" }
    { "method": "fixed", "unit": "week", "steps": 2, "weekdays": ["TU","TH"] }

    method is worth choosing deliberately — with fixed, occurrences appear on schedule whether or not the last was completed, so a missed week leaves a backlog; with start-after-completion, the next occurrence is scheduled from when you actually finish, so the habit simply resumes. Positional rules ("third Tuesday") and end conditions (COUNT/UNTIL) are not supported and report a clear error. See Repeating items.

  • Clients attached to an outdated daemon are told, in-band (#113). After an upgrade, a long-lived client keeps its old daemon indefinitely, and nothing used to say so. While a newer daemon is serving, each tool result now carries a one-line notice to reconnect. Liveness-probed so an orphaned socket can't nag, throttled, and skipped for isolated instances.

Fixes

  • query_omnifocus returns the actual repetition rule (#115). The repetitionRule field emitted the literal string "[object Task.RepetitionRule]" — the object wrapper rather than the rule — so a client could tell that an item repeated but never how. It now returns the ICS string (e.g. FREQ=WEEKLY;INTERVAL=2), alongside a new repetitionMethod field (Fixed / DeferUntilDate / DueDate). Available on tasks and projects.

Full Changelog: v1.13.0...v1.14.0

v1.13.0

Choose a tag to compare

@themotionmachine themotionmachine released this 17 Aug 19:31

What's Changed

Fixes

  • Quotes in item names no longer corrupt write results (#103, #108). Write tools built their result JSON by splicing strings into AppleScript source; a quote in a task name survived source escaping but re-materialized raw in the payload, so the tool reported an error for a successful write — a duplicate-retry hazard. Every write script now double-escapes TS-known values and routes runtime values (item names, AppleScript error text) through a jsonEscape handler.
  • query_omnifocus no longer renders [undefined] for projects when status isn't in the requested fields, and results no longer open with a header + "Filters applied" echo of your own arguments (#106, #110).

Features

  • folderName filter on query_omnifocus for tasks and projects — case-insensitive partial match, includes subfolders; folderId takes precedence when both are given (#107, #112). "All tasks in the X folder" is now one call.
  • Write results echo the item id — add_omnifocus_task, add_project, edit_item, remove_item, and per-item in both batch tools now report (id: …), so follow-up edits don't need a re-query (#104, #109).

Efficiency

  • Tool schema descriptions halved — ~13k → ~7.1k chars (~1.5k tokens saved in every session that loads the server), with no schema shape changes and a budget test to keep them lean (#105, #111).

Full Changelog: v1.12.0...v1.13.0

v1.12.0

Choose a tag to compare

@themotionmachine themotionmachine released this 15 Aug 16:16

What's Changed

Fixes

  • query_omnifocus folders now report their real hierarchy (#95, #101). The path and parentFolderID fields previously read a nonexistent container property, so every folder looked top-level. They now walk parent correctly — matching what dump_database reported all along — and parentFolderID is available as a selectable field.
  • The daemon socket filename now carries the package version (#99, #102): daemon-<version>.sock instead of daemon.sock. Previously, upgrading left newly launched clients silently attached to the old version's daemon — and since the idle reaper only arms at zero connections, that daemon could outlive the upgrade indefinitely. Right after an upgrade you may briefly see two daemons; the old one exits once its last client disconnects.

Cleanup

  • Removed the dead getChangesSince helper (#94, #100) — unreachable code that unconditionally returned empty results.

Full Changelog: v1.11.0...v1.12.0

v1.11.0 — one shared server process

Choose a tag to compare

@themotionmachine themotionmachine released this 11 Aug 15:17

Two changes, both about the server telling you the truth: one shared process instead of N competing ones, and a batch tool that reports where your tasks actually went.

Shared daemon (#80, PR #96)

Running several agents against OmniFocus used to mean several MCP server processes, each with its own concurrency budget aimed at a single-threaded app. Since scriptExecution.ts's osascript semaphore is per-process, N servers allowed N×4 concurrent osascript children — which is a direct cause of the AppleEvent -1712 timeouts that wedge a session. Orphaned servers accumulating over a day made it worse.

Now the published omnifocus-mcp command is a thin shim that pipes stdio onto a Unix domain socket, and a single background daemon hosts one MCP session per connection. The semaphore is shared, so concurrency is bounded at 4 globally no matter how many clients connect.

  • No config change. Your MCP client still launches the same command; the launch contract is identical.
  • No new port and no auth token. The socket lives in a 0700 directory, so access control is the kernel's uid check rather than something this package has to invent — worth caring about, since this server can read and delete your entire task database.
  • Falls back cleanly. If the socket is unusable for any reason — sandbox with no writable runtime dir, exotic filesystem, path length past sun_path — you get exactly the old standalone server, just without the sharing.
  • Idles out. The daemon exits after 30 minutes with no connected clients (OMNIFOCUS_MCP_IDLE_TIMEOUT_MINUTES).
  • Opt out with OMNIFOCUS_MCP_NO_DAEMON=1.

cli.cjs also no longer spawns a child process, which removes a layer from the wrapper chain that made orphaned servers hard to reap.

New environment variables are documented in the README, along with OMNIFOCUS_MCP_MAX_CONCURRENT_OSASCRIPT and the idle timeout, neither of which had been written down before.

batch_add_items honors projectId, and reports placement (#97, PR #98)

batch_add_items accepted projectId on task items only in the sense that nothing rejected it. Zod strips unknown keys by default, so the argument never reached the primitive: every task landed in the Inbox while the tool reported ✅ Successfully added N items. Recovery meant noticing by eye and re-homing each task with edit_item.

Two defects, and the second is why the first went unnoticed:

  1. The schemas had drifted. add_omnifocus_task grew projectId; batch_add_items never did. It's now accepted, forwarded, and takes precedence over projectName, matching the single-item tool.

  2. The batch reported no placement at all. addOmniFocusTask already returned where each task landed; batchAddItems discarded it. So even with the argument plumbed through, the next dropped placement would have looked identical to success. Results now say where each task went:

✅ Successfully added 3 items. ⚠️ 1 landed in the inbox despite a requested project or parent.

- ✅ task: "In the project" → in project "Reading"
- ✅ task: "Deliberately unplaced" → inbox
- ✅ task: "Silently dropped" → ⚠️ inbox — requested "Reading", not honored

Also fixed:

  • A parentTempId pointing at a project created earlier in the same batch now targets it by id rather than by name. A batch creating a project whose name already exists elsewhere would previously attach its children to the wrong one.
  • add_omnifocus_task said in a project when placement came from projectId — withholding the location from exactly the caller who can't eyeball it. It now names the project.

Upgrading

npx omnifocus-mcp@latest, or bump the pinned version in your MCP client config. No configuration changes are required. If you had pkill -f omnifocus-mcp in a maintenance script for the orphan pileup, you shouldn't need it anymore — but it remains safe to run.

Full changelog: v1.10.0...v1.11.0

v1.10.0 — date correctness

Choose a tag to compare

@themotionmachine themotionmachine released this 05 Aug 18:52

Two silent date-corruption bugs fixed, plus project disambiguation on add_omnifocus_task.

Fixed: dates landed a month late (#91, #93)

Any date written on the 29th–31st toward a shorter month landed a month late — via add_omnifocus_task, add_project or edit_item, for due/defer/planned alike. The generated AppleScript scaffolded off current date, so setting the month while the intermediate carried today's day-of-month rolled the date forward, and the later set day couldn't undo it:

today = Jul 31, target = Nov 1
  set month to 11  ->  Nov 31 invalid  ->  rolls to Dec 1
  set day to 1     ->  already 1       ->  stays Dec 1

It reported success every time. Dormant on the 1st–28th, which made it look intermittent.

Fixed: dates read back a day early outside the Americas (#91, #93)

Every date was serialized with toISOString(), rendering local wall-clock times in UTC. In any timezone ahead of UTC, local midnight came back as the previous calendar day:

before after
Europe/London (BST) 2026-09-30T23:00:00.000Z ❌ 2026-10-01T00:00:00+01:00 ✅

The stored data was always correct — it was being reported wrong. Structurally invisible to anyone developing behind UTC, which is why it took a report from @mummynoble to surface.

Important

Output format change. Date fields now carry a local UTC offset instead of a Z suffix. new Date(...) and .slice(0, 10) are unaffected. Exact string comparison against a Z-suffixed literal will need updating.

Added: project disambiguation on add_omnifocus_task (#81, #82)

Thanks to @ahharvey for this one. add_omnifocus_task now has the same targeting parity edit_item had:

  • projectId for exact targeting
  • folder-qualified projectName, e.g. "Work/Community Outreach"
  • projectId takes precedence when both are given

Also

  • #89 — seven project filters that silently no-op'd now work (tags, flagged, dueWithin, deferredUntil, dueOn, deferOn, hasNote), plus three silent-empty bugs: Project.added is never forwarded from the root task, hasNote: false matched nothing, and an unguarded tag access could take down a whole query
  • #90 — a component-less path like "/" now fails loudly instead of silently creating the task in the inbox
  • #88 — task/project parity suite for query_omnifocus, so the next "works for tasks, forgotten for projects" gap fails CI rather than reaching users

Full changelog: v1.9.2...v1.10.0

v1.9.2

Choose a tag to compare

@themotionmachine themotionmachine released this 11 Jun 18:01

Fixes

  • #77 — Project ids now work across query and mutation tools. Project ids returned by query_omnifocus and dump_database previously lived in the OmniJS namespace and could not be fed back into edit_item / remove_item, which resolve against AppleScript project ids. Queries now emit the AppleScript-compatible id (the project's root task id), and projectId filters accept either id form so previously stored ids keep working. (#78)

Verification

Live round trip confirmed against OmniFocus: query a project's id → edit_item rename → remove_item, plus projectId filters matching under both id forms on tasks and projects, and dump_database keying/linkage. CI green on Node 20 and 22.