omnifocus-mcp-enhanced 1.6.10 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +21 -0
- package/LICENSE.md +21 -0
- package/README.md +169 -6
- package/README.zh.md +122 -2
- package/dist/omnifocustypes.js +11 -0
- package/dist/server.js +4 -0
- package/dist/tools/definitions/addOmniFocusTask.js +5 -1
- package/dist/tools/definitions/addProject.js +5 -1
- package/dist/tools/definitions/batchAddItems.js +1 -1
- package/dist/tools/definitions/dumpDatabase.js +9 -1
- package/dist/tools/definitions/dumpDatabase.test.js +52 -2
- package/dist/tools/definitions/editItem.js +5 -1
- package/dist/tools/definitions/listTags.js +18 -0
- package/dist/tools/definitions/plannedDateSchemas.test.js +25 -0
- package/dist/tools/definitions/setRepetitionRule.js +88 -0
- package/dist/tools/definitions/setRepetitionRule.test.js +42 -0
- package/dist/tools/dumpDatabase.js +113 -108
- package/dist/tools/dumpDatabase.test.js +122 -0
- package/dist/tools/primitives/addOmniFocusTask.js +22 -4
- package/dist/tools/primitives/addProject.js +22 -4
- package/dist/tools/primitives/applyTagsExclusive.js +37 -0
- package/dist/tools/primitives/applyTagsExclusive.test.js +19 -0
- package/dist/tools/primitives/editItem.js +35 -3
- package/dist/tools/primitives/editItem.test.js +22 -0
- package/dist/tools/primitives/getForecastTasks.js +30 -9
- package/dist/tools/primitives/getForecastTasks.test.js +67 -0
- package/dist/tools/primitives/listTags.js +33 -0
- package/dist/tools/primitives/listTags.test.js +46 -0
- package/dist/tools/primitives/readTaskAttachment.js +17 -1
- package/dist/tools/primitives/readTaskAttachment.test.js +17 -0
- package/dist/tools/primitives/setRepetitionRule.js +85 -0
- package/dist/tools/primitives/setRepetitionRule.test.js +65 -0
- package/dist/utils/appleScriptString.js +6 -2
- package/dist/utils/appleScriptString.test.js +4 -0
- package/dist/utils/omnifocusScripts/applyTagsExclusive.js +106 -0
- package/dist/utils/omnifocusScripts/forecastTasks.js +16 -21
- package/dist/utils/omnifocusScripts/listTags.js +22 -0
- package/dist/utils/omnifocusScripts/omnifocusDump.js +22 -0
- package/dist/utils/omnifocusScripts/setRepetitionRule.js +117 -0
- package/dist/utils/sanitize.js +45 -0
- package/dist/utils/scriptExecution.js +61 -51
- package/dist/utils/scriptExecution.test.js +9 -0
- package/docs/plans/2026-07-24-maintenance-releases-design.md +43 -0
- package/docs/roadmap/2026-02-25-batch-move-tasks-plan.md +150 -39
- package/docs/roadmap/2026-02-25-batch-move-tasks-plan.zh.md +148 -37
- package/package.json +2 -2
- package/src/omnifocustypes.ts +14 -0
- package/src/server.ts +16 -0
- package/src/tools/definitions/addOmniFocusTask.ts +6 -1
- package/src/tools/definitions/addProject.ts +6 -1
- package/src/tools/definitions/batchAddItems.ts +1 -1
- package/src/tools/definitions/dumpDatabase.test.ts +58 -2
- package/src/tools/definitions/dumpDatabase.ts +10 -1
- package/src/tools/definitions/editItem.ts +6 -1
- package/src/tools/definitions/listTags.ts +21 -0
- package/src/tools/definitions/plannedDateSchemas.test.ts +31 -0
- package/src/tools/definitions/setRepetitionRule.test.ts +53 -0
- package/src/tools/definitions/setRepetitionRule.ts +93 -0
- package/src/tools/dumpDatabase.test.ts +128 -0
- package/src/tools/dumpDatabase.ts +124 -117
- package/src/tools/primitives/addOmniFocusTask.ts +28 -6
- package/src/tools/primitives/addProject.ts +28 -6
- package/src/tools/primitives/applyTagsExclusive.test.ts +23 -0
- package/src/tools/primitives/applyTagsExclusive.ts +56 -0
- package/src/tools/primitives/editItem.test.ts +26 -0
- package/src/tools/primitives/editItem.ts +42 -3
- package/src/tools/primitives/getForecastTasks.test.ts +78 -0
- package/src/tools/primitives/getForecastTasks.ts +35 -11
- package/src/tools/primitives/listTags.test.ts +66 -0
- package/src/tools/primitives/listTags.ts +55 -0
- package/src/tools/primitives/readTaskAttachment.test.ts +36 -0
- package/src/tools/primitives/readTaskAttachment.ts +25 -1
- package/src/tools/primitives/setRepetitionRule.test.ts +76 -0
- package/src/tools/primitives/setRepetitionRule.ts +132 -0
- package/src/types.ts +13 -0
- package/src/utils/appleScriptString.test.ts +6 -0
- package/src/utils/appleScriptString.ts +6 -2
- package/src/utils/omnifocusScripts/applyTagsExclusive.js +106 -0
- package/src/utils/omnifocusScripts/forecastTasks.js +16 -21
- package/src/utils/omnifocusScripts/listTags.js +22 -0
- package/src/utils/omnifocusScripts/omnifocusDump.js +22 -0
- package/src/utils/omnifocusScripts/setRepetitionRule.js +117 -0
- package/src/utils/sanitize.ts +46 -0
- package/src/utils/scriptExecution.test.ts +18 -0
- package/src/utils/scriptExecution.ts +75 -64
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
test:
|
|
13
|
+
runs-on: macos-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-node@v4
|
|
17
|
+
with:
|
|
18
|
+
node-version: 20
|
|
19
|
+
cache: npm
|
|
20
|
+
- run: npm ci
|
|
21
|
+
- run: npm test
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 jqlts1
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -13,10 +13,38 @@
|
|
|
13
13
|
<img width="380" height="200" src="https://glama.ai/mcp/servers/@jqlts1/omnifocus-mcp-enhanced/badge" alt="OmniFocus Enhanced MCP server" />
|
|
14
14
|
</a>
|
|
15
15
|
|
|
16
|
-
Enhanced Model Context Protocol (MCP) server for OmniFocus featuring **native custom perspective access**, hierarchical task display, AI-optimized tool selection, and comprehensive task management.
|
|
16
|
+
Enhanced Model Context Protocol (MCP) server for OmniFocus featuring **native custom perspective access**, hierarchical task display, AI-optimized tool selection, and comprehensive task management.
|
|
17
|
+
|
|
18
|
+
In plain English: this lets your AI assistant read your OmniFocus data, create tasks/projects, organize subtasks, review perspectives, and help you plan work without you manually jumping between apps.
|
|
19
|
+
|
|
20
|
+
## 🌠 Why This Project Exists
|
|
21
|
+
|
|
22
|
+
OmniFocus is already powerful, but it is still mostly a tool you drive by hand.
|
|
23
|
+
|
|
24
|
+
The bigger idea behind this project is simple:
|
|
25
|
+
|
|
26
|
+
- less clicking, more conversation
|
|
27
|
+
- less manual cleanup, more AI-assisted planning
|
|
28
|
+
- less tool memorization, more natural task management
|
|
29
|
+
|
|
30
|
+
The goal is not just to expose more OmniFocus commands.
|
|
31
|
+
The goal is to let you work with OmniFocus like this:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Plan my day.
|
|
35
|
+
Clean up my Inbox.
|
|
36
|
+
Turn these notes into a project.
|
|
37
|
+
Show me what is blocked.
|
|
38
|
+
Reorganize these tasks safely.
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
If that feels natural, this MCP server is doing its job.
|
|
42
|
+
|
|
43
|
+
Want to see where the project is heading next? See the [roadmap](docs/roadmap/2026-02-25-batch-move-tasks-plan.md).
|
|
17
44
|
|
|
18
45
|
## 🆕 Latest Release
|
|
19
46
|
|
|
47
|
+
- **v1.7.0** - Added OmniFocus 4.7+ repeat rule support via `set_repetition_rule` (ICS rule strings, schedule type, anchor date, catch-up, end date, repetition count), mutually exclusive tag support via `exclusiveTags` on add/edit tools, and improved planned-date editing tests.
|
|
20
48
|
- **v1.6.10** - Fixed Inbox task completion via `edit_item`, fixed AppleScript special-character handling for apostrophes/backslashes, fixed JSON result escaping for special characters, and clarified `batch_add_items` / `mcporter` usage with working examples.
|
|
21
49
|
- **v1.6.9** - Added task attachment support: `get_task_by_id` now lists attachment metadata, `dump_database` exports attachment/link metadata, and new `read_task_attachment` returns image attachments as MCP image content when possible.
|
|
22
50
|
- **v1.6.8** - Added stable task move support via `move_task` and `edit_item` (`newProjectId/newProjectName/newParentTaskId/newParentTaskName/moveToInbox`) with duplicate-name protection and cycle-prevention checks.
|
|
@@ -39,19 +67,22 @@ Enhanced Model Context Protocol (MCP) server for OmniFocus featuring **native cu
|
|
|
39
67
|
- **🔄 Full CRUD Operations** - Create, read, update, delete tasks and projects
|
|
40
68
|
- **📅 Time Management** - Due, defer, planned dates, estimates, and scheduling
|
|
41
69
|
- **🏷️ Advanced Tagging** - Tag-based filtering with exact/partial matching
|
|
70
|
+
- **🚫 Mutually Exclusive Tags** - Automatically respects exclusive tag groups when applying tags
|
|
71
|
+
- **🔁 Repeat Rules** - Full OmniFocus 4.7+ repetition support (ICS rules, schedule type, anchor date, catch-up, end date, count)
|
|
42
72
|
- **🤖 AI Integration** - Seamless Claude AI integration for intelligent workflows
|
|
43
73
|
- **🖼️ Attachment-Aware Reads** - Surface note attachments and linked files before deciding whether AI should inspect them
|
|
44
74
|
|
|
45
75
|
## 📦 Installation
|
|
46
76
|
|
|
47
|
-
###
|
|
77
|
+
### Claude Code
|
|
48
78
|
|
|
79
|
+
#### Quick Install (recommended)
|
|
49
80
|
```bash
|
|
50
81
|
# One-line installation
|
|
51
82
|
claude mcp add omnifocus-enhanced -- npx -y omnifocus-mcp-enhanced
|
|
52
83
|
```
|
|
53
84
|
|
|
54
|
-
|
|
85
|
+
#### Alternative methods for Claude Code:
|
|
55
86
|
|
|
56
87
|
```bash
|
|
57
88
|
# Upgrade to latest
|
|
@@ -68,13 +99,137 @@ npm install && npm run build
|
|
|
68
99
|
claude mcp add omnifocus-enhanced -- node "/path/to/omnifocus-mcp-enhanced/dist/server.js"
|
|
69
100
|
```
|
|
70
101
|
|
|
102
|
+
### Claude Desktop / Cowork
|
|
103
|
+
|
|
104
|
+
Add the server to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
105
|
+
|
|
106
|
+
```json
|
|
107
|
+
{
|
|
108
|
+
"mcpServers": {
|
|
109
|
+
"omnifocus-enhanced": {
|
|
110
|
+
"command": "npx",
|
|
111
|
+
"args": ["-y", "omnifocus-mcp-enhanced"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
For a local clone, use:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"mcpServers": {
|
|
122
|
+
"omnifocus-enhanced": {
|
|
123
|
+
"command": "node",
|
|
124
|
+
"args": ["/path/to/omnifocus-mcp-enhanced/dist/server.js"]
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Restart Claude Desktop after editing the config file.
|
|
131
|
+
|
|
132
|
+
### Using Both Claude Code and Claude Desktop / Cowork
|
|
133
|
+
|
|
134
|
+
Claude Code and Claude Desktop read separate configurations. If you want to use this MCP server from both, you need to install it in both places — run `claude mcp add` for Claude Code **and** add the entry to `claude_desktop_config.json` for Claude Desktop / Cowork.
|
|
135
|
+
|
|
71
136
|
## 📋 Requirements
|
|
72
137
|
|
|
73
138
|
- **macOS 10.15+** - OmniFocus is macOS-only
|
|
74
139
|
- **OmniFocus 3+** - The application must be installed and running
|
|
75
140
|
- **OmniFocus Pro** - Required for custom perspectives (new features in v1.6.0)
|
|
76
141
|
- **Node.js 18+** - For running the MCP server
|
|
77
|
-
- **
|
|
142
|
+
- **Any MCP-capable client** - Claude Code, `mcporter`, or another MCP host
|
|
143
|
+
|
|
144
|
+
## 🚦 Start Here
|
|
145
|
+
|
|
146
|
+
If you only want the fastest way to understand this project, remember this:
|
|
147
|
+
|
|
148
|
+
1. Connect the MCP server to your AI client.
|
|
149
|
+
2. Talk to the AI naturally.
|
|
150
|
+
3. Let it read, plan, create, move, or update your OmniFocus tasks for you.
|
|
151
|
+
|
|
152
|
+
You do not need to memorize all tool names first.
|
|
153
|
+
|
|
154
|
+
## 🙋 What This Is Good For
|
|
155
|
+
|
|
156
|
+
- **Daily planning**: ask your AI what is due today, what is flagged, and what you can finish in 30 minutes.
|
|
157
|
+
- **Project setup**: give the AI a rough goal, then let it create a project and break it into subtasks.
|
|
158
|
+
- **Inbox cleanup**: ask it to review Inbox tasks and sort them into next actions, projects, or someday/later buckets.
|
|
159
|
+
- **Perspective reviews**: ask it to open one of your custom perspectives and summarize what matters.
|
|
160
|
+
- **Batch capture**: paste meeting notes or a brainstorm list and let the AI create multiple tasks at once.
|
|
161
|
+
- **Attachment-aware review**: let the AI inspect task attachments only when needed.
|
|
162
|
+
|
|
163
|
+
## 💬 Example AI Conversations
|
|
164
|
+
|
|
165
|
+
These work well in Claude Code or any MCP client that can call the same tools.
|
|
166
|
+
|
|
167
|
+
### 1. Daily Planning
|
|
168
|
+
|
|
169
|
+
Try saying:
|
|
170
|
+
|
|
171
|
+
```text
|
|
172
|
+
Check my Forecast and flagged tasks, then tell me the 3 most important things to do today.
|
|
173
|
+
Prefer tasks that take under 60 minutes first.
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 2. Inbox Cleanup
|
|
177
|
+
|
|
178
|
+
Try saying:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
Review my Inbox and group the tasks into:
|
|
182
|
+
1. do today
|
|
183
|
+
2. schedule later
|
|
184
|
+
3. turn into projects
|
|
185
|
+
Then help me clean up the obvious ones.
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 3. Turn an Idea Into a Project
|
|
189
|
+
|
|
190
|
+
Try saying:
|
|
191
|
+
|
|
192
|
+
```text
|
|
193
|
+
Create a project called "Launch spring newsletter".
|
|
194
|
+
Add the main subtasks, estimated minutes, and mark the most important step as flagged.
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### 4. Use a Custom Perspective
|
|
198
|
+
|
|
199
|
+
Try saying:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
Open my custom perspective "今日工作安排" and summarize:
|
|
203
|
+
- what is due soon
|
|
204
|
+
- what looks blocked
|
|
205
|
+
- what I can finish quickly
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 5. Batch Add From Notes
|
|
209
|
+
|
|
210
|
+
Try saying:
|
|
211
|
+
|
|
212
|
+
```text
|
|
213
|
+
Turn these meeting notes into OmniFocus tasks under the project "Website Refresh".
|
|
214
|
+
Use subtasks where it makes sense and keep the task names short.
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 6. Review Attachments Only When Needed
|
|
218
|
+
|
|
219
|
+
Try saying:
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
Find the task called "Review design draft".
|
|
223
|
+
Show me what attachments it has first.
|
|
224
|
+
Only open the image attachment if there is one.
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## 🧭 Practical Usage Tips
|
|
228
|
+
|
|
229
|
+
- Ask the AI to **look first, then change things** if you want safer workflows.
|
|
230
|
+
- Use **task IDs** when you have duplicate task names.
|
|
231
|
+
- For **subtasks**, let the parent task determine the project. Do not also pass `projectName`.
|
|
232
|
+
- For `mcporter`, complex arrays are much more reliable with `--args '{...}'`.
|
|
78
233
|
|
|
79
234
|
## 🎯 Core Capabilities
|
|
80
235
|
|
|
@@ -470,7 +625,10 @@ get_custom_perspective_tasks {
|
|
|
470
625
|
|
|
471
626
|
## 🔧 Configuration
|
|
472
627
|
|
|
473
|
-
###
|
|
628
|
+
### Claude Code
|
|
629
|
+
|
|
630
|
+
Verify the server is registered:
|
|
631
|
+
|
|
474
632
|
```bash
|
|
475
633
|
# Check MCP status
|
|
476
634
|
claude mcp list
|
|
@@ -482,10 +640,15 @@ get_inbox_tasks
|
|
|
482
640
|
list_custom_perspectives
|
|
483
641
|
```
|
|
484
642
|
|
|
643
|
+
### Claude Desktop / Cowork
|
|
644
|
+
|
|
645
|
+
Open `~/Library/Application Support/Claude/claude_desktop_config.json` and confirm the `omnifocus-enhanced` entry is present under `mcpServers`. Restart the app after any changes. Once running, you can test by asking the assistant to list your inbox tasks or custom perspectives.
|
|
646
|
+
|
|
485
647
|
### Troubleshooting
|
|
486
648
|
- Ensure OmniFocus 3+ is installed and running
|
|
487
649
|
- Verify Node.js 18+ is installed
|
|
488
|
-
-
|
|
650
|
+
- For Claude Code: run `claude mcp list` to confirm the server is registered
|
|
651
|
+
- For Claude Desktop / Cowork: verify `claude_desktop_config.json` is valid JSON and restart the app
|
|
489
652
|
- Enable accessibility permissions for terminal apps if needed
|
|
490
653
|
|
|
491
654
|
## 🎯 Use Cases
|
package/README.zh.md
CHANGED
|
@@ -9,10 +9,38 @@
|
|
|
9
9
|
|
|
10
10
|
> **将 OmniFocus 转换为 AI 驱动的生产力强化工具,支持自定义透视**
|
|
11
11
|
|
|
12
|
-
增强版 OmniFocus 模型上下文协议(MCP)服务器,具备**原生自定义透视访问**、层级任务显示、AI
|
|
12
|
+
增强版 OmniFocus 模型上下文协议(MCP)服务器,具备**原生自定义透视访问**、层级任务显示、AI 优化工具选择和全面的任务管理功能。
|
|
13
|
+
|
|
14
|
+
说人话:它可以让你的 AI 助手直接读取 OmniFocus、创建任务和项目、拆子任务、查看透视、做每日规划,不需要你自己在 OmniFocus 里来回点很多次。
|
|
15
|
+
|
|
16
|
+
## 🌠 这个项目为什么存在
|
|
17
|
+
|
|
18
|
+
OmniFocus 本身已经很强了,但它大多数时候仍然是一个需要你手动操作的工具。
|
|
19
|
+
|
|
20
|
+
这个项目想做的,其实很简单:
|
|
21
|
+
|
|
22
|
+
- 少点很多次按钮,多用自然对话
|
|
23
|
+
- 少做手工整理,多让 AI 帮你规划
|
|
24
|
+
- 少记工具名,多直接说你想完成什么
|
|
25
|
+
|
|
26
|
+
目标不只是继续往外暴露更多 OmniFocus 命令。
|
|
27
|
+
更重要的是,让你以后可以这样使用 OmniFocus:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
帮我规划今天。
|
|
31
|
+
帮我清理 Inbox。
|
|
32
|
+
把这段笔记变成一个项目。
|
|
33
|
+
告诉我哪些事情卡住了。
|
|
34
|
+
把这批任务安全地重新整理一下。
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
如果 README 读到这里,你已经能感受到这个方向,那这段说明就有价值了。
|
|
38
|
+
|
|
39
|
+
如果你想继续看这个项目接下来准备往哪里走,可以直接看[路线图](docs/roadmap/2026-02-25-batch-move-tasks-plan.zh.md)。
|
|
13
40
|
|
|
14
41
|
## 🆕 最新版本
|
|
15
42
|
|
|
43
|
+
- **v1.7.0** - 新增 OmniFocus 4.7+ 重复规则支持(`set_repetition_rule`:ICS 规则、重复方式、锚定日期、自动追平、结束日期、重复次数),新增互斥标签支持(`exclusiveTags`),并补全计划日期编辑测试。
|
|
16
44
|
- **v1.6.10** - 修复 `edit_item` 无法完成 Inbox 任务的问题,修复 AppleScript 对单引号/反斜杠等特殊字符的处理,修复特殊字符导致的 JSON 返回解析失败,并补充 `batch_add_items` / `mcporter` 的可直接运行示例与说明。
|
|
17
45
|
- **v1.6.9** - 新增任务附件支持:`get_task_by_id` 会返回附件元信息,`dump_database` 导出附件/链接元信息,并新增 `read_task_attachment`,可在支持时直接把图片附件作为 MCP 图片内容返回。
|
|
18
46
|
- **v1.6.6** - 新增 Planned Date(计划日期)全链路支持:创建/编辑/读取/过滤/排序/导出,包含 `plannedDate` / `newPlannedDate`。
|
|
@@ -34,6 +62,8 @@
|
|
|
34
62
|
- **🔄 完整 CRUD 操作** - 创建、读取、更新、删除任务和项目
|
|
35
63
|
- **📅 时间管理** - 截止日期、推迟日期、计划日期、估时和计划
|
|
36
64
|
- **🏷️ 高级标签** - 基于标签的精确/模糊匹配过滤
|
|
65
|
+
- **🚫 互斥标签** - 应用标签时自动遵守互斥标签组规则
|
|
66
|
+
- **🔁 重复规则** - 完整支持 OmniFocus 4.7+ 重复任务(ICS 规则、重复方式、锚定日期、自动追平、结束日期、重复次数)
|
|
37
67
|
- **🤖 AI 集成** - 与 Claude AI 无缝集成,实现智能工作流
|
|
38
68
|
- **🖼️ 附件感知读取** - 先暴露备注附件和链接文件的元信息,再决定是否让 AI 继续查看附件内容
|
|
39
69
|
|
|
@@ -69,7 +99,97 @@ claude mcp add omnifocus-enhanced -- node "/path/to/omnifocus-mcp-enhanced/dist/
|
|
|
69
99
|
- **OmniFocus 3+** - 必须安装并运行该应用程序
|
|
70
100
|
- **OmniFocus Pro** - 自定义透视功能需要 Pro 版本(v1.6.0 新功能)
|
|
71
101
|
- **Node.js 18+** - 运行 MCP 服务器
|
|
72
|
-
-
|
|
102
|
+
- **任意支持 MCP 的客户端** - 比如 Claude Code、`mcporter` 或其他 MCP Host
|
|
103
|
+
|
|
104
|
+
## 🚦 先看这里
|
|
105
|
+
|
|
106
|
+
如果你想最快理解这个项目,只要记住这 3 件事:
|
|
107
|
+
|
|
108
|
+
1. 把这个 MCP server 接到你的 AI 客户端里。
|
|
109
|
+
2. 直接用自然语言和 AI 说你要做什么。
|
|
110
|
+
3. 让 AI 帮你读 OmniFocus、整理任务、创建项目、拆子任务、做规划。
|
|
111
|
+
|
|
112
|
+
你一开始不需要背所有工具名。
|
|
113
|
+
|
|
114
|
+
## 🙋 这个项目最适合拿来做什么
|
|
115
|
+
|
|
116
|
+
- **每日规划**:让 AI 看今天到期、已标记、可快速完成的任务。
|
|
117
|
+
- **项目拆解**:给 AI 一个目标,让它自动建项目并拆成子任务。
|
|
118
|
+
- **Inbox 清理**:让 AI 帮你把收件箱分成“今天做 / 以后排 / 变项目”。
|
|
119
|
+
- **透视复盘**:让 AI 打开你的自定义透视并做总结。
|
|
120
|
+
- **批量录入**:把会议纪要、脑暴清单直接变成一批任务。
|
|
121
|
+
- **按需看附件**:先看任务有哪些附件,再决定要不要让 AI 打开。
|
|
122
|
+
|
|
123
|
+
## 💬 和大模型对话的示例
|
|
124
|
+
|
|
125
|
+
下面这些说法,在 Claude Code 或其他支持 MCP 的客户端里都很适合直接用。
|
|
126
|
+
|
|
127
|
+
### 1. 每日规划
|
|
128
|
+
|
|
129
|
+
你可以直接说:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
看看我今天的 Forecast 和已标记任务,然后告诉我今天最重要的 3 件事。
|
|
133
|
+
优先考虑 60 分钟以内能完成的任务。
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2. 清理 Inbox
|
|
137
|
+
|
|
138
|
+
你可以直接说:
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
帮我看一下 Inbox,把这些任务分成:
|
|
142
|
+
1. 今天做
|
|
143
|
+
2. 以后安排
|
|
144
|
+
3. 应该升级成项目
|
|
145
|
+
然后顺手把明显的项整理掉。
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. 把一个想法变成项目
|
|
149
|
+
|
|
150
|
+
你可以直接说:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
创建一个项目,名字叫“春季 newsletter 发布”。
|
|
154
|
+
把主要步骤拆成子任务,补上预计时间,并把最关键的一步设成 flagged。
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### 4. 使用自定义透视
|
|
158
|
+
|
|
159
|
+
你可以直接说:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
打开我的自定义透视“今日工作安排”,帮我总结:
|
|
163
|
+
- 哪些快到期
|
|
164
|
+
- 哪些像是卡住了
|
|
165
|
+
- 哪些可以快速做完
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### 5. 根据笔记批量创建任务
|
|
169
|
+
|
|
170
|
+
你可以直接说:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
把这段会议纪要整理成 OmniFocus 任务,放到“网站改版”项目下。
|
|
174
|
+
该拆成子任务的就拆,任务名尽量简短。
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### 6. 只在需要时查看附件
|
|
178
|
+
|
|
179
|
+
你可以直接说:
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
找到“检查设计稿”这个任务。
|
|
183
|
+
先告诉我它有哪些附件。
|
|
184
|
+
如果里面有图片,再帮我打开图片附件。
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 🧭 实用建议
|
|
188
|
+
|
|
189
|
+
- 如果你想更稳一点,可以先让 AI **先查看,再修改**。
|
|
190
|
+
- 如果有重名任务,优先用 **task ID**。
|
|
191
|
+
- 创建**子任务**时,让父任务决定项目,不要再额外传 `projectName`。
|
|
192
|
+
- 在 `mcporter` 里,复杂数组参数尽量用 `--args '{...}'`。
|
|
73
193
|
|
|
74
194
|
## 🎯 核心功能
|
|
75
195
|
|
package/dist/omnifocustypes.js
CHANGED
|
@@ -18,6 +18,17 @@ export var Task;
|
|
|
18
18
|
RepetitionMethod[RepetitionMethod["Fixed"] = 2] = "Fixed";
|
|
19
19
|
RepetitionMethod[RepetitionMethod["None"] = 3] = "None";
|
|
20
20
|
})(RepetitionMethod = Task.RepetitionMethod || (Task.RepetitionMethod = {}));
|
|
21
|
+
let RepetitionScheduleType;
|
|
22
|
+
(function (RepetitionScheduleType) {
|
|
23
|
+
RepetitionScheduleType[RepetitionScheduleType["Regularly"] = 0] = "Regularly";
|
|
24
|
+
RepetitionScheduleType[RepetitionScheduleType["FromCompletion"] = 1] = "FromCompletion";
|
|
25
|
+
})(RepetitionScheduleType = Task.RepetitionScheduleType || (Task.RepetitionScheduleType = {}));
|
|
26
|
+
let AnchorDateKey;
|
|
27
|
+
(function (AnchorDateKey) {
|
|
28
|
+
AnchorDateKey[AnchorDateKey["DeferDate"] = 0] = "DeferDate";
|
|
29
|
+
AnchorDateKey[AnchorDateKey["DueDate"] = 1] = "DueDate";
|
|
30
|
+
AnchorDateKey[AnchorDateKey["PlannedDate"] = 2] = "PlannedDate";
|
|
31
|
+
})(AnchorDateKey = Task.AnchorDateKey || (Task.AnchorDateKey = {}));
|
|
21
32
|
})(Task || (Task = {}));
|
|
22
33
|
export var Project;
|
|
23
34
|
(function (Project) {
|
package/dist/server.js
CHANGED
|
@@ -13,11 +13,13 @@ import * as batchRemoveItemsTool from './tools/definitions/batchRemoveItems.js';
|
|
|
13
13
|
import * as getTaskByIdTool from './tools/definitions/getTaskById.js';
|
|
14
14
|
import * as readTaskAttachmentTool from './tools/definitions/readTaskAttachment.js';
|
|
15
15
|
import * as getTodayCompletedTasksTool from './tools/definitions/getTodayCompletedTasks.js';
|
|
16
|
+
import * as setRepetitionRuleTool from './tools/definitions/setRepetitionRule.js';
|
|
16
17
|
// Import perspective tools
|
|
17
18
|
import * as getInboxTasksTool from './tools/definitions/getInboxTasks.js';
|
|
18
19
|
import * as getFlaggedTasksTool from './tools/definitions/getFlaggedTasks.js';
|
|
19
20
|
import * as getForecastTasksTool from './tools/definitions/getForecastTasks.js';
|
|
20
21
|
import * as getTasksByTagTool from './tools/definitions/getTasksByTag.js';
|
|
22
|
+
import * as listTagsTool from './tools/definitions/listTags.js';
|
|
21
23
|
// Import ultimate filter tool
|
|
22
24
|
import * as filterTasksTool from './tools/definitions/filterTasks.js';
|
|
23
25
|
// Import custom perspective tools
|
|
@@ -40,11 +42,13 @@ server.tool("batch_remove_items", "Remove multiple tasks or projects from OmniFo
|
|
|
40
42
|
server.tool("get_task_by_id", "Get information about a specific task by ID or name", getTaskByIdTool.schema.shape, getTaskByIdTool.handler);
|
|
41
43
|
server.tool("read_task_attachment", "Read a task attachment reported by get_task_by_id. Images are returned as MCP image content when possible.", readTaskAttachmentTool.schema.shape, readTaskAttachmentTool.handler);
|
|
42
44
|
server.tool("get_today_completed_tasks", "Get tasks completed today - view today's accomplishments", getTodayCompletedTasksTool.schema.shape, getTodayCompletedTasksTool.handler);
|
|
45
|
+
server.tool("set_repetition_rule", "Set, update, or clear the repeat rule on a task. Supports ICS rule strings, schedule type, anchor date, catch-up, end date, and repetition count.", setRepetitionRuleTool.schema.shape, setRepetitionRuleTool.handler);
|
|
43
46
|
// Register perspective tools
|
|
44
47
|
server.tool("get_inbox_tasks", "Get tasks from OmniFocus inbox perspective", getInboxTasksTool.schema.shape, getInboxTasksTool.handler);
|
|
45
48
|
server.tool("get_flagged_tasks", "Get flagged tasks from OmniFocus with optional project filtering", getFlaggedTasksTool.schema.shape, getFlaggedTasksTool.handler);
|
|
46
49
|
server.tool("get_forecast_tasks", "Get tasks from OmniFocus forecast perspective (due/deferred tasks in date range)", getForecastTasksTool.schema.shape, getForecastTasksTool.handler);
|
|
47
50
|
server.tool("get_tasks_by_tag", "Get tasks filtered by OmniFocus tags (labels like @home, @work, @urgent). Use this for tag-based filtering, NOT for custom perspective names. Tags are labels assigned to individual tasks.", getTasksByTagTool.schema.shape, getTasksByTagTool.handler);
|
|
51
|
+
server.tool("list_tags", "List OmniFocus tags with IDs, parent relationships, and active status without loading tasks", listTagsTool.schema.shape, listTagsTool.handler);
|
|
48
52
|
// Ultimate filter tool - The most powerful task perspective engine
|
|
49
53
|
server.tool("filter_tasks", "Advanced task filtering with unlimited perspective combinations - status, dates, projects, tags, search, and more", filterTasksTool.schema.shape, filterTasksTool.handler);
|
|
50
54
|
// Custom perspective tools
|
|
@@ -9,6 +9,7 @@ export const schema = z.object({
|
|
|
9
9
|
flagged: z.boolean().optional().describe("Whether the task is flagged or not"),
|
|
10
10
|
estimatedMinutes: z.number().optional().describe("Estimated time to complete the task, in minutes"),
|
|
11
11
|
tags: z.array(z.string()).optional().describe("Tags to assign to the task"),
|
|
12
|
+
exclusiveTags: z.boolean().optional().describe("Respect mutually exclusive tag groups when applying tags (default: true). When a tag belongs to an exclusive group, sibling tags from that group are removed."),
|
|
12
13
|
projectName: z.string().optional().describe("The name of the project to add the task to (will add to inbox if not specified)"),
|
|
13
14
|
parentTaskId: z.string().optional().describe("The ID of the parent task to create this task as a subtask"),
|
|
14
15
|
parentTaskName: z.string().optional().describe("The name of the parent task to create this task as a subtask (alternative to parentTaskId)")
|
|
@@ -39,10 +40,13 @@ export async function handler(args, extra) {
|
|
|
39
40
|
let plannedDateText = args.plannedDate
|
|
40
41
|
? ` planned for ${new Date(args.plannedDate).toLocaleDateString()}`
|
|
41
42
|
: "";
|
|
43
|
+
let exclusivityText = result.removedSiblings && result.removedSiblings.length > 0
|
|
44
|
+
? `\nRemoved mutually exclusive tags: ${result.removedSiblings.join(', ')}`
|
|
45
|
+
: "";
|
|
42
46
|
return {
|
|
43
47
|
content: [{
|
|
44
48
|
type: "text",
|
|
45
|
-
text: `✅ Task "${args.name}" created successfully ${locationText}${dueDateText}${plannedDateText}${tagText}
|
|
49
|
+
text: `✅ Task "${args.name}" created successfully ${locationText}${dueDateText}${plannedDateText}${tagText}.\n\nid: ${result.taskId}${exclusivityText}`
|
|
46
50
|
}]
|
|
47
51
|
};
|
|
48
52
|
}
|
|
@@ -9,6 +9,7 @@ export const schema = z.object({
|
|
|
9
9
|
flagged: z.boolean().optional().describe("Whether the project is flagged or not"),
|
|
10
10
|
estimatedMinutes: z.number().optional().describe("Estimated time to complete the project, in minutes"),
|
|
11
11
|
tags: z.array(z.string()).optional().describe("Tags to assign to the project"),
|
|
12
|
+
exclusiveTags: z.boolean().optional().describe("Respect mutually exclusive tag groups when applying tags (default: true). When a tag belongs to an exclusive group, sibling tags from that group are removed."),
|
|
12
13
|
folderName: z.string().optional().describe("The name of the folder to add the project to (will add to root if not specified)"),
|
|
13
14
|
sequential: z.boolean().optional().describe("Whether tasks in the project should be sequential (default: false)")
|
|
14
15
|
});
|
|
@@ -33,10 +34,13 @@ export async function handler(args, extra) {
|
|
|
33
34
|
let sequentialText = args.sequential
|
|
34
35
|
? " (sequential)"
|
|
35
36
|
: " (parallel)";
|
|
37
|
+
let exclusivityText = result.removedSiblings && result.removedSiblings.length > 0
|
|
38
|
+
? `\nRemoved mutually exclusive tags: ${result.removedSiblings.join(', ')}`
|
|
39
|
+
: "";
|
|
36
40
|
return {
|
|
37
41
|
content: [{
|
|
38
42
|
type: "text",
|
|
39
|
-
text: `✅ Project "${args.name}" created successfully ${locationText}${dueDateText}${plannedDateText}${tagText}${sequentialText}
|
|
43
|
+
text: `✅ Project "${args.name}" created successfully ${locationText}${dueDateText}${plannedDateText}${tagText}${sequentialText}.\n\nid: ${result.projectId}${exclusivityText}`
|
|
40
44
|
}]
|
|
41
45
|
};
|
|
42
46
|
}
|
|
@@ -36,7 +36,7 @@ export async function handler(args, extra) {
|
|
|
36
36
|
if (item.success) {
|
|
37
37
|
const itemType = args.items[index].type;
|
|
38
38
|
const itemName = args.items[index].name;
|
|
39
|
-
return `- ✅ ${itemType}: "${itemName}"`;
|
|
39
|
+
return `- ✅ ${itemType}: "${itemName}" (id: ${item.id})`;
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
42
42
|
const itemType = args.items[index].type;
|
|
@@ -47,7 +47,7 @@ export function formatCompactReport(database, options) {
|
|
|
47
47
|
// Add legend
|
|
48
48
|
output += `FORMAT LEGEND:
|
|
49
49
|
F: Folder | P: Project | •: Task | 🚩: Flagged
|
|
50
|
-
Dates: [M/D] | [DUE:M/D] [PLAN:M/D] [defer:M/D] | Duration: (30m) or (2h) | Tags: <tag1,tag2>
|
|
50
|
+
Dates: [M/D] | [ADD:M/D] [DUE:M/D] [PLAN:M/D] [defer:M/D] | Duration: (30m) or (2h) | Tags: <tag1,tag2>
|
|
51
51
|
Status: #next #avail #block #due #over #compl #drop\n\n`;
|
|
52
52
|
// Map of folder IDs to folder objects for quick lookup
|
|
53
53
|
const folderMap = new Map();
|
|
@@ -112,6 +112,10 @@ Status: #next #avail #block #due #over #compl #drop\n\n`;
|
|
|
112
112
|
statusInfo = ' [Dropped]';
|
|
113
113
|
}
|
|
114
114
|
// Add due date if present
|
|
115
|
+
if (project.addedDate) {
|
|
116
|
+
const addedDateStr = formatCompactDate(project.addedDate);
|
|
117
|
+
statusInfo += ` [ADD:${addedDateStr}]`;
|
|
118
|
+
}
|
|
115
119
|
if (project.dueDate) {
|
|
116
120
|
const dueDateStr = formatCompactDate(project.dueDate);
|
|
117
121
|
statusInfo += statusInfo ? ` [DUE:${dueDateStr}]` : ` [DUE:${dueDateStr}]`;
|
|
@@ -144,6 +148,10 @@ Status: #next #avail #block #due #over #compl #drop\n\n`;
|
|
|
144
148
|
const flagSymbol = task.flagged ? '🚩 ' : '';
|
|
145
149
|
// Format dates
|
|
146
150
|
let dateInfo = '';
|
|
151
|
+
if (task.addedDate) {
|
|
152
|
+
const addedDateStr = formatCompactDate(task.addedDate);
|
|
153
|
+
dateInfo += ` [ADD:${addedDateStr}]`;
|
|
154
|
+
}
|
|
147
155
|
if (task.dueDate) {
|
|
148
156
|
const dueDateStr = formatCompactDate(task.dueDate);
|
|
149
157
|
dateInfo += ` [DUE:${dueDateStr}]`;
|
|
@@ -71,6 +71,9 @@ test('formatCompactReport respects hideCompleted for inbox tasks', () => {
|
|
|
71
71
|
});
|
|
72
72
|
test('formatCompactReport includes planned date marker for tasks', () => {
|
|
73
73
|
assert.equal(typeof formatCompactReport, 'function');
|
|
74
|
+
const plannedDate = '2026-02-20T12:00:00.000Z';
|
|
75
|
+
const plannedLocal = new Date(plannedDate);
|
|
76
|
+
const plannedMarker = `PLAN:${plannedLocal.getMonth() + 1}/${plannedLocal.getDate()}`;
|
|
74
77
|
const output = formatCompactReport({
|
|
75
78
|
exportDate: '2026-02-12T00:00:00.000Z',
|
|
76
79
|
tasks: [
|
|
@@ -85,7 +88,7 @@ test('formatCompactReport includes planned date marker for tasks', () => {
|
|
|
85
88
|
flagged: false,
|
|
86
89
|
dueDate: null,
|
|
87
90
|
deferDate: null,
|
|
88
|
-
plannedDate
|
|
91
|
+
plannedDate,
|
|
89
92
|
estimatedMinutes: null,
|
|
90
93
|
tagNames: []
|
|
91
94
|
}
|
|
@@ -97,5 +100,52 @@ test('formatCompactReport includes planned date marker for tasks', () => {
|
|
|
97
100
|
hideCompleted: true,
|
|
98
101
|
hideRecurringDuplicates: true
|
|
99
102
|
});
|
|
100
|
-
assert.match(output,
|
|
103
|
+
assert.match(output, new RegExp(plannedMarker));
|
|
104
|
+
});
|
|
105
|
+
test('formatCompactReport includes added date markers for tasks and projects', () => {
|
|
106
|
+
assert.equal(typeof formatCompactReport, 'function');
|
|
107
|
+
const taskAddedDate = '2026-02-10T12:00:00.000Z';
|
|
108
|
+
const projectAddedDate = '2026-02-01T12:00:00.000Z';
|
|
109
|
+
const taskAddedLocal = new Date(taskAddedDate);
|
|
110
|
+
const projectAddedLocal = new Date(projectAddedDate);
|
|
111
|
+
const taskMarker = `ADD:${taskAddedLocal.getMonth() + 1}/${taskAddedLocal.getDate()}`;
|
|
112
|
+
const projectMarker = `ADD:${projectAddedLocal.getMonth() + 1}/${projectAddedLocal.getDate()}`;
|
|
113
|
+
const output = formatCompactReport({
|
|
114
|
+
exportDate: '2026-02-12T00:00:00.000Z',
|
|
115
|
+
tasks: [
|
|
116
|
+
{
|
|
117
|
+
id: 'task-added-1',
|
|
118
|
+
name: 'Review quarterly goals',
|
|
119
|
+
projectId: 'project-added-1',
|
|
120
|
+
parentId: null,
|
|
121
|
+
childIds: [],
|
|
122
|
+
completed: false,
|
|
123
|
+
taskStatus: 'Available',
|
|
124
|
+
flagged: false,
|
|
125
|
+
addedDate: taskAddedDate,
|
|
126
|
+
dueDate: null,
|
|
127
|
+
deferDate: null,
|
|
128
|
+
plannedDate: null,
|
|
129
|
+
estimatedMinutes: null,
|
|
130
|
+
tagNames: []
|
|
131
|
+
}
|
|
132
|
+
],
|
|
133
|
+
projects: {
|
|
134
|
+
'project-added-1': {
|
|
135
|
+
id: 'project-added-1',
|
|
136
|
+
name: 'Quarterly planning',
|
|
137
|
+
status: 'Active',
|
|
138
|
+
folderID: null,
|
|
139
|
+
flagged: false,
|
|
140
|
+
addedDate: projectAddedDate
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
folders: {},
|
|
144
|
+
tags: {}
|
|
145
|
+
}, {
|
|
146
|
+
hideCompleted: true,
|
|
147
|
+
hideRecurringDuplicates: true
|
|
148
|
+
});
|
|
149
|
+
assert.match(output, new RegExp(`P: Quarterly planning \\[${projectMarker}\\]`));
|
|
150
|
+
assert.match(output, new RegExp(`Review quarterly goals \\[${taskMarker}\\]`));
|
|
101
151
|
});
|
|
@@ -17,6 +17,7 @@ export const schema = z.object({
|
|
|
17
17
|
addTags: z.array(z.string()).optional().describe("Tags to add to the task"),
|
|
18
18
|
removeTags: z.array(z.string()).optional().describe("Tags to remove from the task"),
|
|
19
19
|
replaceTags: z.array(z.string()).optional().describe("Tags to replace all existing tags with"),
|
|
20
|
+
exclusiveTags: z.boolean().optional().describe("Respect mutually exclusive tag groups when adding/replacing tags (default: true). When a tag belongs to an exclusive group, sibling tags from that group are removed."),
|
|
20
21
|
newProjectId: z.string().optional().describe("For tasks: move task to this project ID"),
|
|
21
22
|
newProjectName: z.string().optional().describe("For tasks: move task to this project name (errors on duplicate names)"),
|
|
22
23
|
newParentTaskId: z.string().optional().describe("For tasks: move task under this parent task ID"),
|
|
@@ -48,10 +49,13 @@ export async function handler(args, extra) {
|
|
|
48
49
|
if (result.changedProperties) {
|
|
49
50
|
changedText = ` (${result.changedProperties})`;
|
|
50
51
|
}
|
|
52
|
+
let exclusivityText = result.removedSiblings && result.removedSiblings.length > 0
|
|
53
|
+
? `\nRemoved mutually exclusive tags: ${result.removedSiblings.join(', ')}`
|
|
54
|
+
: "";
|
|
51
55
|
return {
|
|
52
56
|
content: [{
|
|
53
57
|
type: "text",
|
|
54
|
-
text: `✅ ${itemTypeLabel} "${result.name}" updated successfully${changedText}
|
|
58
|
+
text: `✅ ${itemTypeLabel} "${result.name}" updated successfully${changedText}.${exclusivityText}`
|
|
55
59
|
}]
|
|
56
60
|
};
|
|
57
61
|
}
|