mcp server
OmniFocus-MCP
Let LLMs interface with your tasks and projects through the Model Context Protocol. Add, organize, and query your OmniFocus database with natural language commands.
Set up
Runs on your computer. Pick your agent and operating system.
Before you start
- Node.js 20 or newer
{
"mcpServers": {
"omnifocus-mcp": {
"command": "npx",
"args": [
"-y",
"omnifocus-mcp"
]
}
}
}same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12
Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs
README
synced Oct 14, 2026OmniFocus MCP Server
A Model Context Protocol (MCP) server that connects OmniFocus to Claude and other MCP-compatible AI assistants.
Overview
This server bridges AI assistants and your OmniFocus database. Through natural conversation, an assistant can query, create, edit, and remove tasks and projects — including bulk operations. Some things you can do with it:
- Translate a syllabus PDF into a fully specified project with tasks, tags, defer dates, and due dates
- Turn a meeting transcript into a list of actions
- Audit and reorganize your tags, projects, and folders conversationally
- Create visualizations of your tasks, projects, and tags
- Process dozens of items in a single batch operation
Quick Start
Prerequisites
- macOS with OmniFocus installed
- Node.js 20 or later (for
npx)
The first time the server talks to OmniFocus, macOS will ask you to allow automation access. Grant it once and you're set.
Claude Desktop
Add the server to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"omnifocus": {
"command": "npx",
"args": ["-y", "omnifocus-mcp"]
}
}
}
Then restart Claude Desktop.
Claude Code
claude mcp add omnifocus -- npx -y omnifocus-mcp
Other MCP clients work the same way: launch npx -y omnifocus-mcp over stdio.
Example Conversations
Targeted queries:
"Show me all my flagged tasks due this week"
"What are my next actions in the Work folder?"
"Count how many tasks are in each project"
Reorganizing:
"I want every task to have an energy level tag. Show me a list of all the tasks that don't have one and your suggestions for what tag to add. I'll make any changes I think are appropriate. Then make the changes in OmniFocus."
Capturing from anywhere:
"Ok, thanks for the detailed explanation of why the rule of law is important. Add a recurring task to my activism project that reminds me to call my representative weekly. Include a summary of this conversation in the notes field."
Working with perspectives:
"What perspectives do I have available?"
"Show me what's in my Inbox perspective"
Processing transcripts or PDFs:
"I'm pasting in the transcript from today's meeting. Please analyze it and create tasks in OmniFocus for any action items assigned to me. Put them in my 'Product Development' project."
Tools
The server provides 12 tools. Optional parameters are marked.
query_omnifocus
Query tasks, projects, or folders with targeted filters — much faster and lighter than dumping the whole database. See QUERY_TOOL_REFERENCE.md for the full reference and QUERY_TOOL_EXAMPLES.md for worked examples.
| Parameter | Description |
|---|---|
entity | What to query: tasks, projects, or folders |
filters (optional) | Combine with AND logic; array filters (tags, status) use OR within the array |
fields (optional) | Only return the listed fields — keeps responses small |
limit, sortBy, sortOrder (optional) | Shape the result list |
includeCompleted (optional) | Include completed/dropped items (default: false) |
summary (optional) | Return only the match count |
Available filters:
- Containers:
projectName(case-insensitive partial match;"inbox"targets the inbox),projectId,folderId(includes subfolders),folderName(case-insensitive partial match, includes subfolders) - Names:
taskName(case-insensitive partial match) - Tags:
tags(exact match, case-sensitive) - Status:
status— tasks:Next,Available,Blocked,DueSoon,Overdue,Completed,Dropped; projects:Active,OnHold,Done,Dropped - Dates, forward-looking:
dueWithin,deferredUntil,plannedWithin(ranges),dueOn,deferOn,plannedOn(exact day). Accept a number of days,"today","tomorrow","this week","next week", or an ISO date - Dates, backward-looking:
addedWithin,addedOn,completedWithin,completedOn,droppedWithin,droppedOn(same value forms; completed/dropped filters requireincludeCompleted: true) - Flags & misc:
flagged,inbox,hasNote,isRepeating(tasks only),reviewDue(projects only)
Filter names are not field names: the filter is inbox, the field is inInbox. Unknown filter keys are rejected, and so are filters or status values that don't apply to the queried entity (folders take only folderId, folderName and status). See the reference for per-entity applicability and exact date semantics.
dump_database
Get the complete state of your database. Use for comprehensive analysis; prefer query_omnifocus for anything targeted.
hideCompleted(optional): hide completed/dropped tasks (default: true)hideRecurringDuplicates(optional): hide duplicate instances of recurring tasks (default: true)
add_omnifocus_task
Create a new task.
nameprojectName(optional): project to add the task to (defaults to inbox)parentTaskId/parentTaskName(optional): nest under an existing tasknote,dueDate,deferDate,plannedDate,flagged,estimatedMinutes,tags(all optional)repeat(optional): make it recurring — see Repeating items
add_project
Create a new project.
namefolderName(optional): folder to place the project insequential(optional): whether tasks must be completed in ordernote,dueDate,deferDate,flagged,estimatedMinutes,tags,repeat(all optional)
edit_item
Edit an existing task or project. Also the way to move items — set newProjectName to move a task into a project, or to ""/"inbox" to send it to the inbox.
idorname: which item to edit (id takes precedence)itemType:taskorproject- Common:
newName,newNote,newDueDate,newDeferDate,newFlagged,newEstimatedMinutes(dates in ISO format; empty string clears) - Tasks:
newStatus(incomplete,completed,dropped,skipped— skipped only for repeating tasks),addTags,removeTags,replaceTags,newProjectName,newPlannedDate - Tasks, nesting and order:
newParentTaskIdputs the task under another task (an action group), keeping its id and anything that links to it;""returns it to the top level of its project, or of the inbox if it has no project.positionplaces it among its siblings:"beginning","end",{"before": "<id>"}or{"after": "<id>"}. - Projects:
newProjectStatus(active,completed,dropped,onHold),newFolderName,newSequential,newReviewInterval({"steps": 2, "unit": "week"}; unitsday,week,month,year),markReviewed(sets the next review date based on the project's review interval, including one set in the same call) - Repetition:
newRepeatsets a new rule (same shape asrepeaton create);newRepeat: nullclears it
Rules for nesting and ordering:
- A task follows its parent. If the parent is in another project or in the inbox, the task moves there too.
positionwithoutnewParentTaskIdreorders the task within its current parent.- A
before/aftertask must be a sibling under the target parent; otherwise the edit fails. - Nesting a task under itself or one of its own subtasks fails, as does naming a project as the parent.
newParentTaskIdcan't be combined withnewProjectName(the parent decides the project).positioncan: the task is moved to the project, then placed.- Both fields are for tasks only, and
newReviewIntervalis for projects only. Passing one on the wrong item type is an error, not a silent skip. - The result names what was stored, e.g.
parent (moved under Plan launch), position (after hZ3kP1x)orreview interval (every 2 weeks).query_omnifocusreports the interval back in the same words (reviewInterval: "2 weeks").
At least one editable field is required — a call with only id/name/itemType is refused rather than reported as a successful no-op. Unrecognized argument keys (a note typo for newNote, say) are rejected by every tool with the key named in the error, instead of being silently dropped.
remove_item
Remove a task or project.
idorname: which item to removeitemType:taskorproject
batch_add_items
Create multiple tasks and projects in one operation. Each item accepts the same fields as add_omnifocus_task / add_project, plus type (task or project) and optional hierarchy helpers:
tempId: a temporary ID other items in the same batch can referenceparentTempId: nest this item under another batch item'stempId
{
"items": [
{ "type": "project", "name": "My Project", "tempId": "proj1" },
{ "type": "task", "name": "First task", "parentTempId": "proj1" },
{ "type": "task", "name": "Parent task", "parentTempId": "proj1", "tempId": "t1" },
{ "type": "task", "name": "Subtask", "parentTempId": "t1" }
]
}
batch_remove_items
Remove multiple tasks or projects in one operation. Each item takes id or name, plus itemType.
list_perspectives
List available perspectives, both built-in and custom (custom perspectives are an OmniFocus Pro feature).
includeBuiltIn,includeCustom(optional, default: true)
get_perspective_view
Get the items visible in a named perspective.
perspectiveName: e.g.Inbox,Flagged, or a custom perspective namelimit(optional, default: 100),includeMetadata(optional),fields(optional)
list_tags
List all tags with their hierarchy, active status, and task counts.
includeDropped(optional, default: false)
create_tag
Create a tag, optionally nested under an existing parent.
nameparentTagName/parentTagID(optional; ID takes precedence)
Repeating items
add_omnifocus_task, add_project, and each item in batch_add_items accept a repeat object; edit_item accepts newRepeat. You describe the schedule and the server compiles the ICS recurrence rule, so you never hand-write an RRULE.
| Field | Description |
|---|---|
method | start-after-completion (counts from when it's actually done), fixed (counts from the calendar regardless), or due-after-completion |
unit | day, week, month, or year |
steps (optional) | Repeat every N units (default 1) |
weekdays (optional) | Specific days, e.g. ["MO","WE","FR"]. Requires unit: "week" |
anchor (optional, fixed only) | Which date the schedule counts from: defer, due, or planned. Default: the due date if the item has one, otherwise the defer date |
catchUp (optional, fixed only) | OmniFocus's "catch up automatically" (default false) |
{ "name": "Weekly review", "repeat": { "method": "start-after-completion", "unit": "week" } }
{ "name": "Strength work", "repeat": { "method": "fixed", "unit": "week", "weekdays": ["TU","TH"] } }
{ "name": "Friday review", "deferDate": "2026-10-02", "repeat": { "method": "fixed", "unit": "week", "weekdays": ["FR"], "anchor": "defer" } }
Pick method deliberately — it's the field most often set wrong by hand. With fixed, occurrences appear on schedule whether or not the last one was done, so a missed week leaves a backlog to clear. With start-after-completion, the next occurrence is scheduled from when you actually complete it, so the habit simply resumes.
Mind the anchor on fixed repeats. A fixed repeat counts from one of the item's dates. If it counts from the due date and the item has no due date, OmniFocus gives it one on completion — a start-date-only habit turns into a deadline. Before v1.17.0 every fixed repeat written through this server was anchored on the due date; now the default follows the item's dates, and anchor makes it explicit. The edit_item result reports the anchor it stored, e.g. repetition (fixed, from defer date). Setting a repeat requires OmniFocus 4.7 or later.
Read a rule back with query_omnifocus using the repetitionRule (ICS string), repetitionMethod, repetitionAnchor, repetitionSchedule, and catchUpAutomatically fields, or filter with isRepeating. repetitionMethod alone reports Fixed for both due- and defer-anchored rules; repetitionAnchor tells them apart.
Not currently supported: positional monthly rules ("third Tuesday"), specific month days, and end conditions (COUNT/UNTIL). Set those in OmniFocus directly.
Resources
Resources let MCP clients attach OmniFocus data to a conversation as context, without tool calls. In Claude Code, type @ to browse them; Claude Desktop and other resource-aware clients can attach them directly. All resources return JSON.
| URI | Description |
|---|---|
omnifocus://inbox | Current inbox items |
omnifocus://today | Today's agenda — due today, planned for today, and overdue |
omnifocus://flagged | All flagged items |
omnifocus://stats | Database statistics (task counts, overdue, flagged, etc.) |
omnifocus://project/{name} | Tasks in a specific project |
omnifocus://perspective/{name} | Items visible in a named perspective |
The two template resources support listing all available values and autocompleting the {name} parameter.
Server Instructions & Logging
Instructions: during the MCP handshake the server sends usage guidance to the client — tool-selection advice (prefer query_omnifocus over dump_database), filter tips, and the resource catalog. No configuration needed.
Logging: the server emits structured logs via the MCP logging protocol. Clients can adjust verbosity with logging/setLevel (debug, info, warning, error, ...). Script execution timing and errors are logged automatically.
How It Works
The server communicates with OmniFocus through osascript, using JXA (JavaScript for Automation) and OmniFocus's embedded Omni Automation (OmniJS) where appropriate. It's built on the official MCP TypeScript SDK and talks to clients over stdio.
Shared daemon
Launching omnifocus-mcp starts a small shim that connects to a shared background daemon, starting one if it isn't already running. Every client on the machine gets its own independent MCP session, but they all run inside that single process.
This matters when several agents use OmniFocus at once. OmniFocus is a single-threaded application driven over AppleEvents, and the server caps how many osascript calls it will run concurrently. When each client ran its own server, that cap was per-process — ten clients meant ten independent budgets aimed at one app, causing AppleEvent timeouts. Sharing one process makes the cap global.
The daemon listens on a Unix domain socket in a 0700 directory (~/.omnifocus-mcp/daemon-<version>.sock by default), so access is enforced by the filesystem — no network port and no token. It exits on its own once no client has been connected for the idle window, and logs to daemon.log beside the socket.
The socket name carries the package version so that upgrading can never leave you talking to the previous version's daemon. Right after an upgrade you may briefly see two daemons: the old one keeps serving the clients already attached to it and exits once the last of them disconnects. Clients still attached to the old daemon are told about it in-band — while a newer daemon is serving, every tool result carries a one-line upgrade notice, so nobody has to remember to reconnect.
A session that simply goes quiet is no longer disconnected: when the idle window passes with no traffic, the shim pings the client and keeps the session if it answers, so long-lived sessions (Claude Code and similar) keep their tools across long quiet stretches. Only a client that never answers is treated as gone.
If the daemon dies while clients are attached — a crash, or a manual kill during troubleshooting — each shim now reconnects, replays its client's initialize handshake to the new daemon, and fails any in-flight requests with a retryable error rather than leaving the client waiting. Previously the shim exited, which cost clients their tools for the rest of the session.
Nothing about client configuration changes. If the daemon can't be started — an unusual sandbox, a read-only home directory — the shim falls back to running a standalone server in-process, exactly as earlier versions did.
Repeating items and occurrence safety
OmniFocus keeps each 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 returns rows that look like duplicates but are history.
query_omnifocus marks them — ⟲ past occurrence — not a duplicate — via an isPastOccurrence field included in the default projection. edit_item and remove_item refuse to mutate one by default, because doing so cascades through the live repeat chain; pass allowPastOccurrence: true if you genuinely mean the historical row.
Note that 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 refusing dotted ids would make repeating projects uneditable.
Environment variables
| Variable | Default | Purpose |
|---|---|---|
OMNIFOCUS_MCP_NO_DAEMON | unset | Set to 1 to skip the daemon entirely and run a dedicated server per client (the pre-daemon behavior). First thing to try if you suspect the daemon. |
OMNIFOCUS_MCP_SOCKET | ~/.omnifocus-mcp/daemon-<version>.sock | Override the socket path, e.g. to run an isolated instance. |
OMNIFOCUS_MCP_IDLE_TIMEOUT_MINUTES | 30 | Idle window after which the server pings the client and exits only if it gets no answer. 0 disables the check. |
OMNIFOCUS_MCP_MAX_CONCURRENT_OSASCRIPT | 4 | Maximum concurrent osascript calls. Lower it if you still see AppleEvent timeouts. |
Roadmap
- MCP
promptsupport - Manipulating notifications for projects and tasks
- See the GitHub issues for feature requests and known issues
Contributing
Contributions are welcome! Please feel free to submit a pull request. CI runs type checking, unit tests, and a build on every PR.
npm install
npm test # unit tests
npm run build # compile to dist/
npm run test:integration # requires OmniFocus; creates and removes TEST:-prefixed items
License
MIT