omnifocus-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| OMNIFOCUS_LOG_LEVEL | No | Log level for the server. Options: info (default), debug (verbose). | info |
| OMNIFOCUS_CACHE_TTL_MS | No | Time-to-live for read cache in milliseconds. Default is 30000 (30 seconds). | 30000 |
| OMNIFOCUS_ALLOW_RAW_SCRIPT | No | Set to 1 to enable raw JXA/OmniJS script execution tools. Off by default. | 0 |
| OMNIFOCUS_ATTACHMENT_PATHS | No | Colon-separated list of allowed attachment paths. Default is $HOME. | $HOME |
Capabilities
Features and capabilities supported by this server
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| prompts | {
"listChanged": true
} |
| resources | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| internal_statusA | Return a health snapshot of the running omnifocus-mcp server. Do NOT use this to read OmniFocus data — prefer task_list, project_list, sync_status, etc. Returns { uptimeMs, ofRunning, lastSync, calendarAccess, mutation, cache, circuits, queueDepth, responseStats, latencyStats, toolDurationStats, stores, transport, density }. cache.services maps key prefixes (tag, folder, forecast, task, project) to { hits, misses, hitRate }. circuits lists each circuit-breaker name and state (closed/open/half_open). ofRunning: null = not probed; use omnifocus_doctor for a live check. lastSync mirrors sync_status data; null if getLastSync throws. calendarAccess: macOS Calendar bridge state — { available, permission: granted|denied|restricted|not-determined|unknown }. Read-only; does NOT trigger TCC prompt. mutation: Stryker mutation-score freshness { score, lastRunAt } (0–100 per ADR-0017); null when no report file is present. responseStats / latencyStats / toolDurationStats: opt-in telemetry — bytes per tool, ms per (transport, script) with spawnFloorMs, ms per tool. Null when sample rate is 0. stores: { idempotencyEntries, loopDetectorKeys } live retention-store sizes — null when not wired. transport: persistent JXA transport stats; enabled=false by default. density: negotiated response density (compact|default|full). Read-only; no side effects. Example: internal_status() |
| webhook_registerA | Register an outbound webhook that fires when an OmniFocus state change matches the supplied trigger. Off by default — requires OMNIFOCUS_WEBHOOKS_ENABLED=1 in the environment, mirroring the raw-script gating. URLs must use https:// (http:// is rejected at registration). An optional secret enables HMAC-SHA256 signature verification by the receiver via X-OmniFocus-Signature: sha256=; the secret is stored on disk only and is never echoed back through any tool response. Do NOT use this to call this MCP server itself — webhooks are outbound only. Returns { webhook: WebhookSummary } where the summary omits both URL and secret. Side effects: writes to the registry config file at ~/Library/Application Support/omnifocus-mcp/webhooks.json (mode 0600). Example: webhook_register({ name: "slack-billing", url: "https://hooks.slack.com/services/...", trigger: { on: "task-completed", filter: { tagId: "tag_xyz" } } }) |
| webhook_listA | List every registered outbound webhook by name, trigger, and createdAt timestamp. URLs and secrets are NEVER surfaced — only metadata safe to display. Use this to confirm what's wired up; delete unwanted entries via webhook_delete. Off by default — requires OMNIFOCUS_WEBHOOKS_ENABLED=1. Do NOT use this to retrieve URLs or secrets — by design they remain on-disk only. Returns { webhooks: WebhookSummary[] } in registration order. Read-only; safe to call repeatedly. Example: webhook_list() |
| webhook_deleteA | Delete a registered outbound webhook by name. Idempotent — returns noChange:true when the named webhook does not exist. Off by default — requires OMNIFOCUS_WEBHOOKS_ENABLED=1. Do NOT use this for bulk-clear operations; this tool removes exactly one entry. Returns { name, deleted:true } or { name, noChange:true }. Side effects: rewrites the registry config file at ~/Library/Application Support/omnifocus-mcp/webhooks.json. Example: webhook_delete({ name: "slack-billing" }) |
| webhook_testA | Fire a synthetic event through a registered webhook to verify it's wired correctly. Goes through the same HTTPS POST + HMAC + retry + circuit-breaker path as a real delivery — if the receiver doesn't see this event, it won't see real ones either. Off by default — requires OMNIFOCUS_WEBHOOKS_ENABLED=1. Do NOT use this for load testing — circuit-breaker counters apply to synthetic events too. Returns { name, delivered: true } on dispatch success, { name, error } when the webhook is not registered. Note: 'delivered' means the dispatcher attempted delivery; the receiver's actual response is not surfaced (per ADR-0016 §4e: failures log to stderr, never throw upward). Side effects: makes one outbound HTTPS POST to the registered URL with a synthetic event payload. Example: webhook_test({ name: "slack-billing" }) |
| folder_createA | Create a new folder in OmniFocus. Optionally nest it inside an existing parent folder (get IDs from folder_list). Do not use to move an existing folder; prefer folder_move instead. Returns the new folder's persistent ID. Triggers a sync; call sync_trigger after to propagate to other devices. Example: folder_create({ name: "Work" }) Example: folder_create({ name: "Archive", parentId: "fld123" }) |
| folder_deleteA | Delete a folder from OmniFocus. By default returns ValidationError when the folder contains projects or subfolders. Pass cascade=true to orphan all direct projects (move to no folder) and recursively delete subfolders before deleting. IRREVERSIBLE — do not use to archive; prefer folder_update to rename instead. Get the folder ID from folder_list. Triggers a sync; call sync_trigger after to propagate to other devices. Example: folder_delete({ id: "fld123" }) Example: folder_delete({ id: "fld123", cascade: true }) |
| folder_getA | Fetch a single folder by its persistent ID, including project and subfolder counts. Do not use to list multiple folders; prefer folder_list instead. Returns folder details including name, parentId, projectCount, and subfolderCount. Safe to call repeatedly; no side effects. Example: folder_get({ id: "fld123" }) |
| folder_listA | List folders in OmniFocus, optionally filtered by parent folder. Do not use to fetch a single folder by ID; prefer folder_get instead. Returns a flat array with projectCount and subfolderCount per folder. Use parentId to walk the hierarchy one level at a time. Safe to call repeatedly; no side effects. Example: folder_list({}) Example: folder_list({ parentId: "fld123" }) |
| folder_moveA | Move a folder to a new parent, or promote it to a root folder by passing parentId=null. Do not use to rename a folder; prefer folder_update instead. Get folder IDs from folder_list. Returns the updated folder's ID and new parentId on success. Triggers a sync; call sync_trigger after to propagate to other devices. Example: folder_move({ id: "fld123", parentId: "fld456" }) Example: folder_move({ id: "fld123", parentId: null }) |
| folder_updateA | Rename a folder (partial patch — only supplied fields are changed). To move a folder use folder_move instead. Get the folder ID from folder_list. Returns the updated folder on success. Triggers a sync; call sync_trigger after to propagate to other devices. Example: folder_update({ id: "fld123", name: "Personal" }) |
| folder_create_describeA | Preview what folder_create would do without making any changes. Do NOT use to actually create a folder — use folder_create instead. Returns { description, plannedChanges } describing the folder that would be created. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| folder_delete_describeA | Preview what folder_delete would do without making any changes. Do NOT use to actually delete a folder — use folder_delete instead. Returns { description, plannedChanges } describing the deletion that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| folder_move_describeA | Preview what folder_move would do without making any changes. Do NOT use to actually move a folder — use folder_move instead. Returns { description, plannedChanges } describing the reparenting that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| folder_update_describeA | Preview what folder_update would do without making any changes. Do NOT use to actually update a folder — use folder_update instead. Returns { description, plannedChanges } showing the fields that would be patched. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| tag_createA | Create a new tag in OmniFocus. Optionally nest it under an existing parent tag (get IDs from tag_list). Do not use to move an existing tag; prefer tag_move instead. Returns the new tag's persistent ID. Triggers a sync; call sync_trigger after to propagate to other devices. Example: tag_create({ name: "errands" }) Example: tag_create({ name: "home", parentId: "tag123" }) |
| tag_deleteA | Hard-delete a tag from OmniFocus. IRREVERSIBLE — the tag and all its children are removed. Tasks that carried this tag lose it. Get the tag ID from tag_list. Prefer tag_set_status with status='dropped' to preserve history. Returns the deleted tag's ID on success. Side effects: writes to OmniFocus, sets meta.syncPending = true. Example: tag_delete({ id: "tag123" }) |
| tag_get_manyA | Fetch up to 100 tags by persistent ID in a single OmniFocus round-trip. Use when you have a set of tag IDs and need full tag objects for all of them. Do NOT use for a single ID — use tag_get instead. Returns Tag[] in input order. Missing IDs are omitted and appear in meta.warnings. Read-only; safe to retry. Example: tag_get_many({ ids: ["tag123", "tag456"] }) |
| tag_getA | Fetch a single tag by its persistent ID, including task count. Do not use to list multiple tags; prefer tag_list instead. Returns tag details; no side effects. Example: tag_get({ id: "tag123" }) |
| tag_get_locationA | Get the geographic location trigger currently set on a tag, or null if none. Do not use to set or clear a location; prefer tag_set_location instead. Location-based tags are an OmniFocus Pro feature. Get the tag ID from tag_list. Returns { location } with name, radius, and trigger direction, or null if unset. Safe to call repeatedly; no side effects. Example: tag_get_location({ id: "tag123" }) |
| tag_listA | List all tags in OmniFocus, optionally filtered by parent tag or status. Do not use to fetch a single tag by ID; prefer tag_get instead. Returns a flat array — use parentId to walk the hierarchy one level at a time. Safe to call repeatedly; no side effects. Example: tag_list({}) Example: tag_list({ status: "active", parentId: "tag123" }) |
| tag_moveA | Move a tag to a new parent, or promote it to a root tag by passing parentId=null. Do not use to rename a tag; prefer tag_update instead. Get tag IDs from tag_list. Returns the updated tag's ID and new parentId on success. Triggers a sync; call sync_trigger after to propagate to other devices. Example: tag_move({ id: "tag123", parentId: "tag456" }) Example: tag_move({ id: "tag123", parentId: null }) |
| tag_set_allows_next_actionA | Enable or disable next-action selection for a tag in OmniFocus. When true, tasks with this tag are eligible for next-action promotion. Do not use to change other tag properties; prefer tag_update instead. Get the tag ID from tag_list. Returns the updated tag with allowsNextAction confirmed. Triggers a sync; call sync_trigger after to propagate to other devices. Example: tag_set_allows_next_action({ id: "tag123", allowsNextAction: true }) |
| tag_set_locationA | Set a geographic location trigger on a tag (OmniFocus Pro only). The trigger fires when entering, leaving, or both for the specified radius. Do not use to read the current location; prefer tag_get_location instead. Get the tag ID from tag_list. Returns FeatureRequiresPro on OmniFocus Standard installs. Triggers a sync; call sync_trigger after to propagate to other devices. Example: tag_set_location({ id: "tag123", latitude: 37.785, longitude: -122.407, radiusMeters: 200, trigger: "entering", name: "Office" }) |
| tag_set_statusA | Set the lifecycle status of a tag to active, on-hold, or dropped. Dropped tags are hidden in OmniFocus but not deleted. Do not use to permanently remove a tag; prefer tag_delete instead. Get the tag ID from tag_list. Returns the updated tag with the confirmed status. Triggers a sync; call sync_trigger after to propagate to other devices. Example: tag_set_status({ id: "tag123", status: "on-hold" }) Example: tag_set_status({ id: "tag123", status: "active" }) |
| tag_updateA | Update mutable fields on an existing tag (partial patch). Only supplied fields are changed; omit a field to leave it unchanged. Do not use to move a tag to a different parent; prefer tag_move instead. Get the tag ID from tag_list. Returns the updated tag on success. Triggers a sync; call sync_trigger after to propagate to other devices. Example: tag_update({ id: "tag123", name: "shopping" }) Example: tag_update({ id: "tag123", status: "dropped" }) |
| tag_create_describeA | Preview what tag_create would do without making any changes. Do NOT use to actually create a tag — use tag_create instead. Returns { description, plannedChanges } describing the tag that would be created. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| tag_delete_describeA | Preview what tag_delete would do without making any changes. Do NOT use to actually delete a tag — use tag_delete instead. Returns { description, plannedChanges } describing the permanent deletion that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| tag_move_describeA | Preview what tag_move would do without making any changes. Do NOT use to actually move a tag — use tag_move instead. Returns { description, plannedChanges } describing the reparenting that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| tag_update_describeA | Preview what tag_update would do without making any changes. Do NOT use to actually update a tag — use tag_update instead. Returns { description, plannedChanges } showing the fields that would be patched. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| note_appendA | Append text to the plain-text note on a task or project. Adds a newline between existing content and the new text unless the note is empty. Do not use to replace the note entirely; prefer note_set instead. Pass idempotency_key to coalesce retries — append is not naturally idempotent and replays without a key duplicate the text. Returns { updated: true, id, targetKind, name, note } — name is the parent task/project's display name (captured from the same read that fetched the existing note) so the agent can describe the change without a follow-up read; note is the full content after appending. Side effects: writes to OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need the change to appear on other devices. Example: note_append({ targetKind: "task", id: "abc123", text: "Follow up next week" }) |
| note_getA | Read the plain-text note from a task or project. Do not use when formatting fidelity matters; prefer note_get_html instead. Returns { note } — a string (may be empty) or null when no note exists. Set targetKind to 'task' and provide a task ID, or 'project' and a project ID. Safe to call repeatedly; no side effects. Example: note_get({ targetKind: "task", id: "abc123" }) |
| note_get_htmlA | Read the HTML fragment from a task or project note. Returns { noteHtml } — an HTML string (may be empty) or null when no note exists. Known limitation: OmniFocus 4.x's automation bridge cannot read notes as HTML, so noteHtml degrades to null there even when a note exists — use note_get for plain text. Set targetKind to 'task' and provide a task ID, or 'project' and a project ID. For plain-text access without formatting, use note_get instead. Safe to call repeatedly; no side effects. Example: note_get_html({ targetKind: "task", id: "abc123" }) |
| note_setA | Replace the plain-text note on a task or project. Overwrites the existing note entirely. Pass note: null to clear the note. To add text without overwriting use note_append instead. Returns { updated: true, id, targetKind, name, note } — name is the parent task/project's display name (pre-fetched so the response describes the change without a follow-up read); note echoes back the final content after writing (or null if cleared). Side effects: writes to OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need the change to appear on other devices. Example: note_set({ targetKind: "task", id: "abc123", note: "Check with Alice first" }) |
| note_set_htmlA | Replace the HTML fragment note on a task or project. Overwrites the existing note entirely with the provided HTML. OmniFocus preserves its supported HTML subset (bold, italic, links, lists, inline images); unsupported elements may be stripped. Pass noteHtml: null to clear the note. Known limitation: OmniFocus 4.x's automation bridge rejects HTML note writes, so this tool fails there with OF_UNSUPPORTED — use note_set (plain text) instead. For plain-text writes use note_set instead. Returns { updated: true, id, targetKind, name, noteHtml } — name is the parent task/project's display name (pre-fetched so the response describes the change without a follow-up read); noteHtml echoes back the requested HTML (or null if cleared). Side effects: writes to OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need the change to appear on other devices. Example: note_set_html({ targetKind: "task", id: "abc123", noteHtml: "Priority: high" }) |
| search_queryA | Full-text search across OmniFocus task names and/or notes. Use for finding tasks by content when you don't know the ID. Supports optional filters (project, tags, flagged, completion status) and cursor pagination. Do NOT use when a known task ID is available (use task_get instead). Returns tasks[] with pagination; safe to call repeatedly; no side effects. Example: search_query({ q: "dentist" }) Example: search_query({ q: "report", projectId: "prj123", completed: "exclude" }) |
| forecast_getA | Get forecast-view tasks from OmniFocus grouped by category: overdue, dueToday, deferredToday, flagged. Use this for 'what's on my plate today' or multi-day planning queries. Do NOT use to list all tasks across all projects; prefer task_list instead. Supply date (ISO-8601 or shortcut like 'today', 'tomorrow') and days (1–7) for the ergonomic interface, or use from/to for exact ISO-8601 ranges. All include flags default to true; set to false to omit a category. When days > 1, response also includes byDate[] grouping task IDs per calendar day (dereference from dueToday[]). Returns { overdue[], dueToday[], deferredToday[], flagged[], byDate? } plus pagination { hasMore, cursor }; byDate entries are { date, taskIds[] }. Pages span the union of all four buckets (a task in multiple buckets counts once); default 50 tasks per page (max 200). Safe to call repeatedly; no side effects. Example: forecast_get({ date: "today" }) Example: forecast_get({ date: "today", days: 3, includeFlagged: false }) Example: forecast_get({ date: "today", limit: 25, cursor: "" }) |
| forecast_packA | Pack today's forecast tasks into a time budget. Use when the user asks 'I have N hours; what should I do?' or wants a focused subset of forecast tasks that fit a limited window. Do NOT use for the full forecast — prefer forecast_get for that. Do NOT use to schedule work across multiple days — pass scope='next7' as a hint, but the pack is still budget-bounded; for true multi-day planning use forecast_get with days>1 and let the agent compose. Pass budgetMinutes (1–1440) and optional filter { tagIds?, scope? }; scope is 'today' (default) or 'next7'. Returns { selected[], totalMinutes, skipped[] }. selected[] are the picks in execution order (flagged first, then dueDate ascending, then stable by ID). skipped[] surfaces tasks the agent should ask the user about: { reason: 'no-estimate' } means the task has no estimatedMinutes so couldn't be packed; { reason: 'exceeds-budget' } means it would have fit individually but was bumped by earlier higher-priority picks. Read-only; no side effects; safe to retry. Pack algorithm is greedy — predictable and explainable beats optimal-by-1-minute. Example: forecast_pack({ budgetMinutes: 120 }) Example: forecast_pack({ budgetMinutes: 240, filter: { tagIds: ["tag123"], scope: "today" } }) |
| forecast_get_tagA | Read the OmniFocus forecast-tag preference: the single tag whose tasks always appear on the Forecast view alongside dated items. Use when the agent needs to answer 'what tag is the user using as their daily agenda?' or to confirm a tag before composing follow-up queries against it. Do NOT use to list tags in general — prefer tag_list. Takes no arguments. Returns { tagId: string | null, name: string | null } — name is the tag's display name (or null when tagId is null or the tag has been deleted) so the agent can describe the forecast tag without a follow-up tag_get. Read-only; no side effects; safe to retry. Backed by OmniJS Database.forecastTag.Example: forecast_get_tag() |
| forecast_set_tagA | Set or clear the OmniFocus forecast-tag preference. Use when the user wants to designate (or change) the tag whose tasks should always appear on Forecast — common during onboarding flows or context switches ('use @today as my agenda'). Do NOT use to add tags to a task — prefer task_update. Pass tagId as a TagId string to set, or null to clear. Returns { tagId: string | null, name: string | null } echoing what was applied — name is paired with the tag id so the agent can describe the change without a follow-up tag_get. Errors: NOT_FOUND when the supplied tagId does not exist. Side effects: mutation; invalidates the forecast read cache. Backed by OmniJS Database.forecastTag. Example: forecast_set_tag({ tagId: "tag123" }) Example: forecast_set_tag({ tagId: null }) |
| perspective_listA | List all perspectives in OmniFocus — both built-in (Inbox, Projects, Tags, Forecast, Flagged, Nearby, Review) and custom (OmniFocus Pro). Do not use to evaluate a perspective; prefer perspective_evaluate for that. Returns each perspective's id, name, kind (builtin|custom), and requiresPro flag. Safe to call repeatedly; no side effects, no writes. Example: perspective_list() |
| perspective_evaluateA | Evaluate an OmniFocus perspective and return its task list. Accepts both built-in ids (inbox, projects, tags, forecast, flagged, nearby, review) and custom-perspective ids obtained from perspective_list — the tool selects the correct transport internally (JXA for built-in, OmniJS for custom). Custom perspectives require OmniFocus Pro; otherwise returns an error with code OF_FEATURE_REQUIRES_PRO. Returns { tasks: Task[] } with cursor pagination (limit defaults to 50, max 200). For 'review', returns [] — use review_list_due instead. For 'nearby', returns [] (location unavailable). No side effects; read-only. Example: perspective_evaluate({ perspectiveId: "flagged" }) Example: perspective_evaluate({ perspectiveId: "flagged", limit: 50, cursor: "…" }) |
| perspective_evaluate_dry_runA | Preview a proposed OmniFocus perspective rule tree without persisting it. Creates a temporary perspective with the supplied rules, evaluates it, and always deletes the temp perspective inside one OmniJS execution. Pairs with perspective_create for the propose-then-save flow used by the perspective-author prompt: propose rules → preview matched tasks via this tool → commit via perspective_create. Custom perspectives require OmniFocus Pro; otherwise returns OF_FEATURE_REQUIRES_PRO. Do NOT use to evaluate a saved perspective — use perspective_evaluate. Returns { tasks: Task[] }. Side effects: creates and immediately deletes a sentinel-named temp perspective inside one OmniJS execution; the database state is unchanged after the call returns. Example: perspective_evaluate_dry_run({ aggregation: 'all', rules: [{ actionStatus: 'flagged' }] }) |
| perspective_getA | Read the full configuration of a custom OmniFocus perspective — name, top-level rule aggregation (all/any/none), the structured rule tree, and icon color (when set). Use to introspect what a perspective filters on before evaluating it, or as a building block for cloning / duplicating perspectives. Do not use on built-in perspectives (inbox, projects, tags, forecast, flagged, nearby, review) — they have no rule tree and the call returns a validation error. Use perspective_list instead to enumerate available perspectives. Custom perspectives require OmniFocus Pro; without it the call returns OF_FEATURE_REQUIRES_PRO. Returns { perspective: { id, name, aggregation, rules, iconColor } }. Safe to call repeatedly; no side effects, no writes. Example: { "perspectiveId": "fOpKrtZBLaZ" } → { perspective: { id, name: "Daily Triage", aggregation: "any", rules: [...], iconColor: { r: 0.2, g: 0.5, b: 0.9, a: 1 } } }. |
| perspective_deleteA | Delete a custom OmniFocus perspective by id. Use when a perspective is no longer needed — e.g. cleaning up after a templated workflow, or rotating out a stale view. Do not use on built-in perspectives (inbox, projects, tags, forecast, flagged, nearby, review) — they cannot be deleted; the call returns a validation error. Custom perspectives require OmniFocus Pro; without it the call returns OF_FEATURE_REQUIRES_PRO. Deletion is permanent — there is no undo for perspective removal in OmniFocus, so confirm with the user before invoking on a perspective they may want to keep. Recommend a sync_trigger after deletion so other devices observe the change. Returns { id } echoing the deleted identifier. Side effects: writes to OmniFocus (removes the perspective from the document), sets meta.syncPending = true. Example: { "perspectiveId": "fOpKrtZBLaZ" } → { id: "fOpKrtZBLaZ" }. |
| perspective_createA | Create a new custom OmniFocus perspective with the given name, optional rule tree, optional aggregation, and optional icon color. The shell is created via JXA |
| perspective_updateA | Partial-patch update of a custom OmniFocus perspective. Only fields present in the input are written — omitting a field leaves the existing value unchanged. Passing iconColor: null clears the custom color back to the OmniFocus default; passing rules: [] clears the rule tree to 'show everything'. Use to rename a perspective, retune its rule tree, swap the aggregation, or recolor the icon. Do NOT use to create a new perspective (prefer |
| plugin_invokeA | Invoke a named Omni Automation plug-in action in OmniFocus. Use this when you need to run a specific installed plug-in — not for built-in OmniFocus operations. Do NOT use to run arbitrary JavaScript; for raw scripting use run_omnijs_script (requires opt-in env var). |
| sync_statusA | Return the last OmniFocus sync state without triggering a new sync. Do NOT call this to initiate a sync — use sync_trigger instead. Use to check whether a previous sync completed before querying cross-device data. Returns { lastSyncAt, inFlight }. lastSyncAt is null if OmniFocus has never synced in this session. Read-only; no side effects. Example: sync_status() |
| sync_triggerA | Kick off an OmniFocus sync with Omni Sync Server. Do not call when no mutations have been made; prefer checking meta.syncPending first. Call this after any sequence of mutations (task_create, task_update, folder_create, etc.) when you need changes to appear on other devices. The sync starts immediately but completes asynchronously — this tool does not block until done. Returns meta.syncPending = false to confirm the sync was initiated. Side effects: triggers a sync request to Omni Sync Server. Example: sync_trigger() |
| changes_sinceA | Incremental sync feed: return what changed since the last call. Call with no args to bootstrap (returns every task/project in |
| database_undoA | Reverse the most recent document mutation, identical to ⌘Z in OmniFocus. Walks back one entry on the document's undo stack regardless of mutation source — an MCP undo can revert a manual UI edit if that was the most recent change. Mandatory |
| database_redoA | Re-apply the most recently undone mutation, identical to ⌘⇧Z in OmniFocus. Advances one entry on the document's redo stack. Any mutation between an undo and a redo invalidates the redo stack (matching UI semantics). Mandatory |
| review_list_dueA | List projects due for review in OmniFocus — those whose next review date is today or earlier, or has never been set. Only remaining projects (active or on-hold) are eligible; completed and dropped projects are never due — matches OmniFocus's Review perspective. Sorted by next review date ascending (overdue first, never-reviewed first). Do not use to get all projects; prefer project_list for that. Returns each project's id, name, nextReviewDate, lastReviewDate, and reviewIntervalDays. Safe to call repeatedly; no side effects, no writes. Example: review_list_due() |
| review_mark_reviewedA | Mark a project as reviewed in OmniFocus — sets lastReviewDate to now and advances nextReviewDate by the project's review interval. Use this after completing a weekly review of a project. Do not use to change the review interval; prefer review_set_interval for that. Returns { id, name, lastReviewDate, nextReviewDate } — name is the project's display name (post-mutation lookup; null if the project has been deleted between write and read), and the dates echo back the new schedule so the agent can describe the result without a follow-up read. Side effects: writes to OmniFocus; sets syncPending = true. Example: review_mark_reviewed({ id: "prj123" }) |
| project_mark_reviewedA | Convenience alias for review_mark_reviewed — mark a single project as reviewed, setting lastReviewDate to now and advancing nextReviewDate. Use when you have a project id and want a single-call review operation. Do not use to list projects due for review; prefer review_list_due for that. Returns { id, name, lastReviewDate, nextReviewDate } — name is the project's display name (post-mutation lookup; null if the project has been deleted between write and read), and the dates echo back the new schedule so the agent can describe the result without a follow-up read. Side effects: writes to OmniFocus; sets syncPending = true. Example: project_mark_reviewed({ id: "prj123" }) |
| review_set_intervalA | Set a project's review interval in OmniFocus — updates how many days between reviews. Use null to remove the recurring schedule. Do not use to mark a project as reviewed; prefer review_mark_reviewed for that. Returns { id, name, reviewIntervalDays } — name is the project's display name (post-mutation lookup; null if the project has been deleted), and reviewIntervalDays echoes back the new value (or null when cleared) so the agent can describe the change without a follow-up read. Side effects: writes to OmniFocus; sets syncPending = true. Example: review_set_interval({ id: "prj123", days: 7 }) Example: review_set_interval({ id: "prj123", days: null }) |
| project_set_next_review_dateA | Set or reset a project's next review date directly. Use when the user wants to reschedule a review independent of the recurring interval — 'push the Q3 review to next Monday' without changing the cadence. Do NOT use to mark a project as reviewed (prefer review_mark_reviewed) or to change the recurring interval (prefer review_set_interval). Pass projectId and nextReviewDate (ISO-8601 with offset), or pass null for nextReviewDate to reset the date to the interval-derived schedule (last review date + review interval) — OmniFocus cannot leave a project unscheduled, so null does not clear the date. Past-dated values are accepted and surface the project as overdue immediately — matches OmniFocus's own UX. Returns { id, name, nextReviewDate } — name is the project's display name (post-mutation lookup; null if the project has been deleted), and nextReviewDate echoes back the project's new value (the recomputed schedule date when null was passed) so the agent can describe the change without a follow-up read. Errors: NOT_FOUND when projectId does not exist. Side effects: writes to OmniFocus; invalidates project + review caches; sets syncPending = true. Example: project_set_next_review_date({ projectId: "prj123", nextReviewDate: "2026-05-05T00:00:00-05:00" }) Example: project_set_next_review_date({ projectId: "prj123", nextReviewDate: null }) |
| export_opmlA | Export OmniFocus data as OPML XML — a structured outline format OmniFocus can import. Do NOT use to export a single task; OPML scope is project-level or broader. Three scopes: 'project' (one project + its tasks), 'folder' (all projects in a folder), or 'all' (all active projects). Returns { opml, projectCount, taskCount } where opml is a complete XML string. Safe to call repeatedly; no side effects. Example: export_opml({ scope: "project", id: "abc123" }) Example: export_opml({ scope: "all" }) |
| import_opmlA | Import tasks from an OPML XML string into OmniFocus. Parses the OPML produced by export_opml and recreates the task hierarchy. Top-level elements are matched to existing projects by OmniFocus ID (for round-trip) then by name; unmatched project outlines land in the Inbox. LOSSY: due dates, defer dates, and flagged state are preserved; tags, notes, attachments, and repetition rules are silently dropped (not encoded in OPML). Do NOT use to export data; prefer export_opml for that. Returns { imported, tasks: [{ id, name }] } — imported is the count of tasks created and tasks pairs each new id with its display name (resolved via a single getTasksMany batch, no N+1) so the agent can confirm what landed without a follow-up read. Orphan ids (rare; the task was deleted between import and lookup) are dropped from the array. Writes to OmniFocus; call sync_trigger after import to propagate changes to other devices. Example: import_opml({ opml: "..." }) |
| export_taskpaperA | Export OmniFocus data as TaskPaper plain text. Three scopes: 'project' (one project + its tasks), 'folder' (all projects in a folder), or 'all' (all active projects). Export is lossy — HTML notes are downgraded to plain text; tag locations, attachments, and complex repetition rules are omitted. Lossiness warnings are returned in meta.warnings. Do NOT use to import data; prefer import_taskpaper for that. Returns { taskpaper, projectCount, taskCount }. Safe to call repeatedly; no side effects. Example: export_taskpaper({ scope: "project", id: "abc123" }) Example: export_taskpaper({ scope: "all" }) |
| import_taskpaperA | Import tasks from TaskPaper text into OmniFocus. Parses '- Task name @tag @due(2026-01-15) @defer(2026-01-10) @flagged' lines. Indented subtasks become children of the nearest parent task. Project headings ('Project name:') map to existing OF projects by name — unrecognised headings fall back to inbox (warning emitted). Unknown @tags are created automatically. Do NOT use to export data; prefer export_taskpaper for that. Returns { tasks: [{ id, name }], warnings: string[] } — tasks pairs each new id with its display name (resolved via a single getTasksMany batch, no N+1) so the agent can confirm what landed without a follow-up read. Orphan ids (rare; deleted between import and lookup) are dropped from the array. Writes to OmniFocus; call sync_trigger to propagate changes to other devices. Example: import_taskpaper({ text: "- Buy milk @errands\n- Call dentist @due(2026-05-01)" }) |
| app_launchA | Explicitly launch OmniFocus. Do NOT call this automatically — only invoke when the user explicitly asks to open OmniFocus; prefer other tools when OF is already running. Safe to call when OmniFocus is already running (idempotent). Returns { launched, alreadyRunning } — launched=true means OmniFocus was not running and was started; alreadyRunning=true means it was already open. Side effects: may open OmniFocus and bring it to the foreground. Example: app_launch() |
| omnifocus_doctorA | Self-diagnostic for omnifocus-mcp setup. Probes server health and the live OmniFocus connection. Do NOT call as a substitute for the tool that actually does the work — only use to triage why another tool is failing; prefer internal_status when you already know setup is fine and only want server metrics. Returns { summary: 'ok' | 'degraded' | 'failed', checks: [{ name, status: 'pass' | 'warn' | 'fail', details, remediation }] }. summary is the worst status across all checks; surface each check's remediation back to the user verbatim. No side effects; will NOT launch OmniFocus (use app_launch for that). Example: omnifocus_doctor() |
| window_get_stateA | Read the active perspective and focus container of the front OmniFocus window. UI-affecting tool family — only meaningful in pair-assistant flows where the user is looking at OmniFocus. Headless agents should ignore. Use when the agent needs to know what view the user currently sees, or to confirm that a prior |
| window_set_perspectiveA | Switch the front OmniFocus window to a named perspective (built-in or custom). UI-affecting tool — only meaningful when the user can see OmniFocus. Headless agents should not fire this. Use when the user asks 'show me my flagged tasks' or a guided weekly-review prompt wants to navigate the user's UI. Do NOT use to evaluate a perspective's results — prefer perspective_evaluate, which doesn't touch the user's UI. Pass perspectiveName (case-sensitive, matches OF's UX). Built-in names: Inbox, Projects, Tags, Forecast, Flagged, Review, Nearby, Completed, Changed. Returns { perspectiveName }. Errors: OF_WINDOW_UNAVAILABLE (no front window), OF_NOT_FOUND (no perspective with this name). Side effects: changes the user's visible window state; no data caches invalidated. Example: window_set_perspective({ perspectiveName: "Flagged" }) |
| window_set_focusA | Set or clear the front OmniFocus window's focus container (a project or folder). UI-affecting tool — only meaningful when the user can see OmniFocus. Headless agents should not fire this. Use when the user asks 'focus on this project' or a guided flow wants to scope the visible view. Do NOT use to filter task data — prefer |
| app_window_newA | Open a new OmniFocus window via OmniJS document.newWindow(). UI-affecting tool — only meaningful when OmniFocus is running. Headless agents should not fire this. Use when the user asks 'open a new window' or a flow needs a fresh, unfocused OmniFocus window. Do NOT use to read task or project data — prefer task_list or project_list instead. Takes no arguments. Returns { perspectiveName: string | null, focusContainerIds: string[] } describing the new window's initial state. Errors: WINDOW_OPEN_FAILED when the window could not be created. Side effects: opens a new OmniFocus window; no data caches invalidated. Example: app_window_new() |
| app_window_new_tabA | Open a new tab on the front OmniFocus window via OmniJS document.newTabOnWindow(). UI-affecting tool — only meaningful when OmniFocus has an open window. Headless agents should not fire this. Use when the user asks 'open a new tab' or a flow needs an additional view in the existing window. Do NOT use to open a standalone window — prefer app_window_new instead. Takes no arguments. Returns { perspectiveName: string | null, focusContainerIds: string[] } describing the new tab's initial state. Errors: WINDOW_UNAVAILABLE when there is no open OmniFocus window; WINDOW_OPEN_FAILED when the tab could not be created. Side effects: opens a new tab in the front OmniFocus window; no data caches invalidated. Example: app_window_new_tab() |
| project_batch_completeA | Mark many OmniFocus projects as completed in a single JXA round trip. Completed projects are hidden from active views and closed to new task entry. Validation is atomic: if any input fails schema, the whole batch is rejected before any mutation. Execution is best-effort: each completion succeeds or fails independently, and the response reports per-index outcomes. Prefer this tool over repeated project_complete calls whenever completing more than one project. Each item is { id }. Returns { completed: [{index, value: { id, name }}], failed: [{index, errorCode, message}] } — value carries the project name so the agent can describe each completion without a follow-up read. Side effects: writes to OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need changes to appear on other devices. Example: project_batch_complete({ items: [{ id: "prj123" }, { id: "prj456" }] }) |
| project_batch_dropA | Cancel (drop) many OmniFocus projects in a single JXA round trip. Dropped projects remain in OmniFocus but are treated as cancelled/inactive — they do not appear in active project lists. Use project_delete for permanent removal. Validation is atomic: if any input fails schema, the whole batch is rejected before any mutation. Execution is best-effort: each drop succeeds or fails independently, and the response reports per-index outcomes. Prefer this tool over repeated project_drop calls whenever dropping more than one project. Each item is { id }. Returns { dropped: [{index, value: { id, name }}], failed: [{index, errorCode, message}] } — value carries the project name so the agent can describe each drop without a follow-up read. Side effects: writes to OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need changes to appear on other devices. Example: project_batch_drop({ items: [{ id: "prj123" }, { id: "prj456" }] }) |
| project_completeA | Complete an OmniFocus project — marks it done with today's date and moves it out of the active view. Use when a project is finished. Do not use to archive or hide a project without completing it; prefer project_drop for that. Returns { completed: true, id, name } — name lets the agent describe the change without a follow-up read. Side effects: sets completionDate, removes from active projects, sets meta.syncPending = true. Example: project_complete({ id: "prj123" }) |
| project_createA | Create a new OmniFocus project. Optionally place it in a folder, assign tags, set completion criterion, status, defer/due dates, estimated minutes, flagged state, and review interval. Safety control: pass idempotency_key to make transport retries safe — identical subsequent calls within the TTL window replay the original envelope with meta.idempotentReplay = true instead of creating a duplicate project. Returns { created: true, id }. Side effects: creates a project in OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need the project to appear on other devices. Example: project_create({ name: "Website Redesign" }) Example: project_create({ name: "Q3 Planning", folderId: "fld123", flagged: true }) |
| project_deleteA | Permanently delete an OmniFocus project and ALL its contained tasks. IRREVERSIBLE — uses OmniFocus deleteObject; there is no undo. All tasks inside the project are also permanently deleted (cascade). Prefer project_drop when you want a recoverable status change. Only use project_delete when the agent has explicit user intent to permanently remove the project and its tasks. Safety controls: set dry_run=true to preview without mutating; pass expectedModifiedAt (from a recent project_get) to reject the call if the project changed since you read it; pass idempotency_key to coalesce retries so the same delete is only performed once. Returns { deleted: true, id } on success. Side effects: removes the project and its tasks from OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need the deletion to appear on other devices. Example: project_delete({ id: "prj123", dry_run: true }) Example: project_delete({ id: "prj123", expectedModifiedAt: "2026-04-01T10:00:00Z" }) |
| project_dropA | Drop an OmniFocus project — marks it as on-hold/dropped and removes it from the active view without completing it. Use to defer or abandon a project while keeping it recoverable. Do not use if the project is actually done; prefer project_complete for that. Returns { dropped: true, id, name } — name lets the agent describe the change without a follow-up read. Side effects: changes project status, sets meta.syncPending = true.Example: project_drop({ id: "prj123" }) |
| project_get_manyA | Fetch up to 100 projects by persistent ID in a single OmniFocus round-trip. Use when you have a set of project IDs and need full project objects for all of them. Do NOT use for a single ID — use project_get instead. Returns Project[] in input order. Missing IDs are omitted and appear in meta.warnings. Read-only; safe to retry. Example: project_get_many({ ids: ["prj123", "prj456"] }) |
| project_getA | Fetch a single OmniFocus project by persistent ID. Do NOT use for queries across projects — use project_list. When includeTaskTree=true (default), the project's flat task list is attached. Returns { project, tasks? }; safe to call repeatedly; no side effects. Example: project_get({ id: "prj123" }) Example: project_get({ id: "prj123", includeTaskTree: false }) |
| project_listA | List projects in OmniFocus with optional filters. Use for queries across projects. Do NOT use for a known single project (use project_get). Filters: folderId, status, flagged, reviewDueBefore. Returns projects[] with pagination; safe to call repeatedly; no side effects. Example: project_list({}) Example: project_list({ status: "active", folderId: "fld123" }) |
| project_moveA | Move an OmniFocus project to a different folder. Pass folderId to move into a folder, or null to move to the root (no folder). Use when reorganizing projects. Do not use to complete or drop a project. Returns { moved: true, id, name } — name lets the agent describe the change without a follow-up read. Side effects: changes the project's folder, sets meta.syncPending = true.Example: project_move({ id: "prj123", folderId: "fld456" }) Example: project_move({ id: "prj123", folderId: null }) |
| project_updateA | Partially update mutable fields on an OmniFocus project. Only supplied fields are changed; omit a field to leave it unchanged. Pass null for note, deferDate, dueDate, estimatedMinutes, or reviewIntervalDays to clear those fields. Do NOT use to create or delete projects; prefer project_create or project_delete instead. Safety controls: set dry_run=true to preview without mutating; pass expectedModifiedAt (from a recent project_get) to reject the call if the project changed since you read it; pass idempotency_key to coalesce retries so the same update is only performed once. Returns { updated: true, id, name } — name reflects the post-patch name. Side effects: writes to OmniFocus, sets meta.syncPending = true. Call sync_trigger when you need changes to appear on other devices. Example: project_update({ id: "prj123", name: "New Name", flagged: true }) Example: project_update({ id: "prj123", status: "on-hold", dry_run: true }) |
| project_template_saveA | Capture a project as a reusable template under the Templates folder (env OMNIFOCUS_TEMPLATES_FOLDER_NAME). Metadata is stored in a fenced YAML block at the top of the template-project note; TaskPaper body sits below. Do NOT use to duplicate a one-off project — prefer task_duplicate. Returns { templateId, templateName, capturedAt }. Side effects: writes folder + project; sets meta.syncPending = true. Example: { projectId: "p_001", templateName: "Client onboarding", parameterNames: ["client"] }. |
| project_template_listA | List saved project templates under the Templates folder. Projects without a parseable template fence are skipped. Do NOT use to enumerate ordinary projects — call project_list. Returns { templates: [{ templateId, templateName, parameterNames, capturedAt }] }, sorted by capturedAt desc. Read-only; safe to retry. Example: call with no args; receives [] when no Templates folder exists yet. |
| project_template_deleteA | Delete a saved project template by name from the Templates folder. Returns { deleted: true, templateName } on success. Returns TemplateNotFoundError when no matching template exists — callers can distinguish 'deleted' from 'never existed'. Side effects: removes the template project; sets meta.syncPending = true. Do NOT use to delete ordinary projects — call project_delete. Example: { templateName: "Client onboarding" }. |
| project_template_instantiateA | Spawn a new project from a saved template under the Templates folder. Substitutes {{name}} placeholders with the supplied parameters and shifts @due / @defer dates relative to the optional dueDate anchor (the earliest @due in the template). Do NOT use to copy a one-off project — prefer task_duplicate. Returns { projectId, taskCount, importWarnings }. Side effects: writes a new project + tasks; sets meta.syncPending = true. Example: { templateName: "Client onboarding", parameters: { client: "Acme" }, dueDate: "2026-06-04" }. |
| project_complete_describeA | Preview what project_complete would do without making any changes. Do NOT use to actually complete a project — use project_complete instead. Returns { description, plannedChanges } describing the completion that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| project_create_describeA | Preview what project_create would do without making any changes. Do NOT use to actually create a project — use project_create instead. Returns { description, plannedChanges } describing the project that would be created. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| project_delete_describeA | Preview what project_delete would do without making any changes. Do NOT use to actually delete a project — use project_delete instead. Returns { description, plannedChanges } describing the permanent deletion that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| project_drop_describeA | Preview what project_drop would do without making any changes. Do NOT use to actually drop a project — use project_drop instead. Returns { description, plannedChanges } describing the status change that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| project_move_describeA | Preview what project_move would do without making any changes. Do NOT use to actually move a project — use project_move instead. Returns { description, plannedChanges } describing the folder change that would occur. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| project_update_describeA | Preview what project_update would do without making any changes. Do NOT use to actually update a project — use project_update instead. Returns { description, plannedChanges } showing the fields that would be patched. No side effects: read-only by contract — never mutates OmniFocus. Example: dry-run companion — pass the same args you would to the write tool, inspect plannedChanges, then call the write tool once approved. |
| task_getA | Fetch a single OmniFocus task by persistent ID. Use when you have a known task ID and need its full detail. Do NOT use for multiple IDs — use task_get_many instead. Returns the Task object plus subtaskIds[] and subtaskCount (when includeSubtasks omitted or false). Pass includeSubtasks: true to get full subtask bodies; use task_get_many to fetch specific subtasks by ID. Read-only; safe to retry. Example: task_get({ id: "abc123" }) |
| task_listA | List tasks in OmniFocus with optional filters (project, tag, inbox, flagged, completion, due dates). Use inbox=true to fetch unprocessed Inbox tasks. Use this for filter-based queries across tasks. Do NOT use for a known single task (use task_get). For name-based lookup, prefer task_find_by_name. For full-text content search across names and notes, prefer search_query. Returns tasks[] with pagination; safe to call repeatedly; no side effects. Example: task_list({ inbox: true }) Example: task_list({ projectId: "prj123", flagged: true }) Example: task_list({ dueBefore: "2026-05-01T00:00:00Z", completed: "exclude" }) |
| task_find_by_nameA | Find tasks in OmniFocus by name. Returns ALL matching tasks (names are not unique in OmniFocus). Names collide in OmniFocus; prefer task_get with an ID when you have one. Use search_query instead when you need to search task notes as well, or want full-text content search. Zero matches returns an empty array — not an error. Returns tasks[]; safe to call repeatedly; no side effects. Example: task_find_by_name({ name: "Buy milk" }) Example: task_find_by_name({ name: "report", matchMode: "contains" }) |
| task_find_similarA | Lexical nearest-neighbour search for de-duplicating tasks. Pass a candidate name (and optional note) and receive the top-K most-similar existing tasks ranked by a deterministic [0, 1] lexical-signal score (Jaccard token-overlap + prefix bonus + exact-name boost). Title-dominant: a perfect title match outranks a perfect note match. Use BEFORE task_create when you suspect a duplicate; the agent inspects the candidates and decides whether to create new, link to existing, or merge. Excludes completed and dropped tasks by default; opt-in via includeCompleted: true. Optional scope { projectId } or { tagId } narrows the candidate set. Returns { candidates: [{ taskId, name, score, project, tags }] } sorted by score descending — project is { id, name } | null and tags is [{ id, name }, ...]. Names are paired alongside ids via a single getProjectsMany + single getTagsMany batch (no N+1) so the agent can describe each candidate without a follow-up read. An empty result is { candidates: [] }, not an error. Do NOT use this tool for general full-text search — call task_search for that. Prefer this helper when the question is 'is this task already in the system?'. No model calls; no side effects. Read-only. Example: task_find_similar({ name: "Call dentist" }) Example: task_find_similar({ name: "Write report", scope: { projectId: "prj123" }, topK: 5 }) |
| task_searchA | Search OmniFocus tasks by keyword and/or structured filters, with cursor pagination. q is optional — omit it to filter by tag, project, date range, or availability alone. When q is supplied, scans task names and/or notes (controlled by scope) for a case-insensitive substring match. Narrow results with: projectId, tagIds (task must carry ALL listed tags), available, dueBefore, dueAfter, flagged, and completed. At least one of q, projectId, tagIds, available, dueBefore, or dueAfter must be provided. Do NOT use when you already have an ID — prefer task_get instead. Returns tasks[] with pagination (limit defaults to 50, max 500); safe to call repeatedly; no side effects. Example: task_search({ q: "dentist" }) Example: task_search({ tagIds: ["tag123"], available: true, dueBefore: "2026-05-01T00:00:00Z" }) |
| task_get_manyA | Fetch up to 100 tasks by persistent ID in a single OmniFocus round-trip. Use when you have a set of task IDs from multiple sources and need full task objects for all of them. Do NOT use for a single ID — use task_get instead. Do NOT use when you only have names — use task_find_by_name. Returns Task[] in input order. Missing IDs are omitted and appear in meta.warnings. Read-only; safe to retry. Example: task_get_many({ ids: ["abc123", "abc456"] }) |
| task_parse_transport_textA | Parse OmniFocus transport text DSL into structured task objects — no tasks are created. Supports @tag, #due-date, ::defer-date, !!, and //note tokens; a leading 'Project: Name' line sets the project context for subsequent tasks. Do not use this tool to create tasks; pass the returned tasks[] to task_create separately. Returns tasks[] with name, tagNames, dueDate, deferDate, flagged, note, and projectName fields, plus count and an optional warnings[] for unparseable dates. Tag names and project names are raw strings — resolve to IDs with tag_list before passing to task_create. Read-only; no side effects. Example: task_parse_transport_text({ text: "Buy milk @errands !!\nWrite report #2026-05-01" }) |
| clarifyA | Replay dispatcher for clarification-needed responses. When a tool returns { kind: 'clarification-needed' }, present the question and options to the user, then call this tool with the replayToken from that response and the zero-based index of the option the user selected. The server resumes the original tool call with the disambiguation applied and returns the final result envelope. Tokens are single-use and expire after 5 minutes — call this tool promptly after the user responds. Passing an expired or unknown token returns a NotFound error. Passing a choice index outside the valid range returns an InvalidInput error. Example: clarify({ replayToken: "tok_abc", choice: 0 }) |
| repetition_from_proseA | Deterministic prose-to-RepetitionRule helper. Pass a natural-language phrase ('every Monday', 'every 3 days', 'first Tuesday of every month') and receive a structured RepetitionRule plus a normalized description to confirm with the user. Returns one of three shapes: { kind: 'ok', rule, normalizedDescription } when the prose maps to one rule; { kind: 'ambiguous', interpretations[] } when prose admits multiple valid readings (typically 2-3) — agent picks one with the user; { kind: 'error', reason, suggestion? } for no-repetition-detected or unsupported-pattern. Supported patterns: daily/weekly/monthly/yearly, every-N-days/weeks/months/years, every weekday/weekend, every {Mon|Tue|...}, nth-weekday-of-month, nth-day-of-month, completion-relative phrasing ('after I complete it'). Time-of-day and end-conditions surface in normalizedDescription only — the canonical RepetitionRule schema doesn't carry those fields. Do NOT use this tool when the agent already has a structured RepetitionRule from another source — call task_set_repetition directly instead. Prefer this helper over ad-hoc LLM translation whenever the user's repetition phrasing is the only signal. No model calls; no side effects. Use with task_set_repetition or task_create. Example: repetition_from_prose({ prose: "every Monday" }) Example: repetition_from_prose({ prose: "every 3 days after I complete it" }) |
| decision_recordA | Record agent memory of user judgment on a task or project — kind, reason, and an optional auto-expiry. Writes a |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| daily-review | Run a daily OmniFocus triage: load snapshot + overdue + forecast/today, reschedule or drop overdue tasks, confirm due-today tasks, unflag low-priority flagged tasks, and process the inbox. No parameters required. |
| weekly-review | Run a weekly OmniFocus review: walk every project due for review, check its tasks, mark it reviewed or complete/drop it, and clean up stale tasks. No parameters required. |
| capture-meeting | Extract action items from meeting notes and create OmniFocus tasks. Pass raw notes as `notes`; optionally target a project with `projectId`. Tasks land in the inbox when projectId is omitted. |
| project-planning | Create a new OmniFocus project and populate it with tasks derived from a brief. Pass `name` and `brief`; optionally place it in a folder with `folderId`. |
| inbox-triage | Triage the OmniFocus inbox in one user confirmation. The agent reads the inbox, proposes a structured assignment per task (project, tags, defer/due, flagged), presents the proposals as a table, and on user approval fires task_batch_assign. Does NOT auto-confirm — the user's approval is the gating step. No parameters required. |
| perspective-author | Translate a free-text description into a saved OmniFocus custom perspective. Walks the agent through three steps: (1) propose a PerspectiveRule[] tree from the prose (asking ONE disambiguation question if genuinely ambiguous, else proposing directly), (2) preview the matched tasks via perspective_evaluate_dry_run, (3) save via perspective_create only after user confirmation. Embeds a reference card of common rule-tree atoms so the agent has the vocabulary without web access. Custom perspectives require OmniFocus Pro. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| omnifocus-capabilities | Structured capabilities object for this omnifocus-mcp server instance. Read once at session start to discover available features (Pro vs Standard), transport timeouts, rate limits, calendar-bridge availability, and whether raw-script tools are enabled. ofVersion and ofEdition are 'unknown'/'standard' until the lazy OF probe runs. |
| omnifocus-snapshot | Orientation snapshot of the current OmniFocus state: inboxCount, overdueCount, dueTodayCount, flaggedCount, reviewDueCount, and syncStatus { lastSyncAt, inFlight }. Read at session start to orient before calling task_list or forecast_get. Use syncStatus.lastSyncAt to detect stale data before making decisions. |
| omnifocus-inbox | Inbox tasks as Task[]. Incomplete tasks not assigned to any project or parent task. Use to triage the inbox without calling task_list. |
| omnifocus-forecast-today | Today's forecast tasks grouped by category: overdue[], dueToday[], deferredToday[], flagged[]. Equivalent to forecast_get with from/to=today. Use for 'what's on my plate today' without a tool call. |
| omnifocus-overdue | All overdue tasks as Task[], sorted by dueDate ascending. Tasks whose dueDate is in the past and are not completed/dropped. |
| omnifocus-flagged | All flagged available tasks as Task[]. Equivalent to task_list with flagged=true. Use to review the flagged list without a tool call. |
| omnifocus-review-due | Projects due for review as Project[], sorted by nextReviewDate ascending. Equivalent to review_list_due. Use to start a review session without a tool call. |
| omnifocus-tasks-inbox | Inbox tasks as Task[]. Alias for omnifocus://inbox using the unified tasks namespace. Returns incomplete tasks not assigned to any project or parent task. |
| omnifocus-waiting-on | All tasks with structured waiting-on metadata, sorted by daysOverdue descending. Each item: { taskId, name, whom, what?, since, followUpAfter?, daysOverdue }. daysOverdue is the integer number of whole days past followUpAfter (0 same-day, null when followUpAfter is unset or still in the future). Use to surface stalled follow-ups without scanning every task. Read-only; safe to retry. Set waiting-on with task_set_waiting_on; clear with task_clear_waiting_on. |
| omnifocus-taxonomy-audit | Taxonomy audit: detects tag and project name collisions (exact duplicates, case differences, plural/singular variants, near-duplicates with Levenshtein ≤ 2 or token-set equality). Returns { tagCollisions: TagCollision[], projectCollisions: ProjectCollision[] }. Each collision lists the candidate names and a reason. Use to identify naming drift and plan merge operations. Empty sections return [], never omitted. |
| omnifocus-intents | Curated routing table mapping human-style user phrases to canonical tool / prompt / resource sequences. Eighty registered tools, eight verbs (capture, plan, review, triage, retrospect, share, audit, automate). Read at session start — or when uncertain which tool fits the user's intent — to discover obvious paths. The agent is NOT constrained by this resource; it's a fallback for ambiguity, not a gatekeeper. Each intent has a phrase, aliases, a one-sentence description in the user's voice, and an ordered sequence of steps (tool calls, prompts, or resource reads). Steps may carry template `args` placeholders the agent fills. Read-only. |
| omnifocus-stats | Server-side aggregate counts for the OmniFocus database — tasks, projects, inbox, tags, sync. Use for 'how is my system doing?' queries (weekly review, daily standup, capacity planning) instead of listing every task and tallying client-side. Returns tasks { total, available, blocked, deferred, completed_today, completed_this_week, overdue_count, flagged_count, dropped_today }, projects { total, active, on_hold, completed, dropped, stalled_count, due_for_review_count }, inbox { count, oldest_age_days }, tags { total, with_tasks_count }, database { sync_age_seconds, last_sync_at }. Stalled = active project with ≥ 14 days since last task activity and no future defer date. Read-only. |
Latest Blog Posts
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/torsday/omnifocus-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server