@kydycode/todoist-mcp-server-ext 0.1.0 → 0.3.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/README.md +111 -105
- package/dist/index.js +459 -26
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,74 +1,86 @@
|
|
|
1
1
|
# Enhanced Todoist MCP Server Extended
|
|
2
|
-
[](https://smithery.ai/server/@kydycode/todoist-mcp-server-ext)
|
|
3
3
|
|
|
4
4
|
> **Extended Version** - Forked and enhanced by [kydycode](https://github.com/kydycode) from the original [@abhiz123/todoist-mcp-server](https://github.com/abhiz123/todoist-mcp-server)
|
|
5
5
|
|
|
6
|
-
A comprehensive MCP (Model Context Protocol) server implementation that provides full integration between Claude and Todoist. This **extended version** includes additional features, improved compatibility, and enhanced functionality using the complete Todoist API with the latest MCP SDK.
|
|
7
|
-
|
|
8
|
-
<a href="https://glama.ai/mcp/servers/fhaif4fv1w">
|
|
9
|
-
<img width="380" height="200" src="https://glama.ai/mcp/servers/fhaif4fv1w/badge" alt="Todoist Server MCP server" />
|
|
10
|
-
</a>
|
|
6
|
+
A comprehensive MCP (Model Context Protocol) server implementation that provides full integration between Claude and Todoist. This **extended version** includes additional features, improved compatibility, and enhanced functionality using the complete Todoist API with the latest MCP SDK (`@doist/todoist-api-typescript@4.0.4`).
|
|
11
7
|
|
|
12
8
|
## 🆕 Extended Version Features
|
|
13
9
|
|
|
14
10
|
### 🔧 **Technical Improvements**
|
|
15
|
-
* **Updated
|
|
16
|
-
* **
|
|
17
|
-
* **
|
|
18
|
-
* **
|
|
19
|
-
* **
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
* **
|
|
24
|
-
* **
|
|
25
|
-
* **
|
|
26
|
-
* **Task
|
|
27
|
-
* **Task
|
|
28
|
-
* **
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* **
|
|
33
|
-
* **
|
|
34
|
-
* **
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
* **
|
|
39
|
-
* **
|
|
40
|
-
|
|
41
|
-
|
|
11
|
+
* **Updated Todoist SDK**: Now using `@doist/todoist-api-typescript@4.0.4`.
|
|
12
|
+
* **Updated MCP SDK Compatibility**: Compatible with MCP SDK 0.5.0.
|
|
13
|
+
* **Enhanced Error Handling**: Comprehensive error handling with detailed error messages.
|
|
14
|
+
* **Improved TypeScript Support**: Better type safety and compatibility.
|
|
15
|
+
* **Optimized API Usage**: Efficient use of Todoist API, including `getTasksByFilter` for robust search and `moveTasks` for semantic task movement.
|
|
16
|
+
* **Better Response Formatting**: Enhanced task, project, and label formatting for better readability, including project names in search results.
|
|
17
|
+
|
|
18
|
+
### ✨ **Enhanced Task Management (10 Tools)**
|
|
19
|
+
* **Direct ID-based Operations**: Efficient task operations using task IDs.
|
|
20
|
+
* **Comprehensive Task Creation**: Support for subtasks, labels, projects, sections, priorities.
|
|
21
|
+
* **Quick Add Integration**: Natural language task creation using Todoist's Quick Add.
|
|
22
|
+
* **Advanced Task Search**: Robust keyword search using Todoist's filter engine (`search: your query`).
|
|
23
|
+
* **Task Movement Capabilities**: Move tasks between projects, sections, or make them subtasks.
|
|
24
|
+
* **Task State Management**: Complete, reopen, and manage task lifecycle.
|
|
25
|
+
* **Detailed Task Output**: Search and get-task operations return more task details.
|
|
26
|
+
|
|
27
|
+
### 🗂️ **Complete Project Management (5 Tools)**
|
|
28
|
+
* **Full Project CRUD**: Create, read, update, delete projects with all properties.
|
|
29
|
+
* **Sub-project Support**: Create hierarchical project structures.
|
|
30
|
+
* **Project Customization**: Set colors, favorites, view styles (list/board).
|
|
31
|
+
* **Enhanced Project Listing**: Improved project retrieval with pagination and detailed formatting.
|
|
32
|
+
|
|
33
|
+
### 📋 **Section Management (4 Tools)**
|
|
34
|
+
* **Complete Section Operations**: Create, read, update, delete sections.
|
|
35
|
+
* **Project-specific Sections**: Filter and manage sections within projects.
|
|
36
|
+
* **Section Organization**: Proper ordering and structure management.
|
|
37
|
+
|
|
38
|
+
### 🏷️ **Label Management (5 Tools)**
|
|
39
|
+
* **Full Label CRUD**: Create, read, update, delete labels.
|
|
40
|
+
* **Label Customization**: Set names, colors, favorites, order.
|
|
41
|
+
* **Paginated Label Listing**: Efficiently retrieve all labels.
|
|
42
|
+
|
|
43
|
+
## 🛠️ Available Tools (Total 24)
|
|
42
44
|
|
|
43
45
|
### Task Operations (10 tools)
|
|
44
|
-
| Tool
|
|
45
|
-
|
|
46
|
-
| `todoist_create_task`
|
|
47
|
-
| `todoist_quick_add_task`
|
|
48
|
-
| `todoist_get_tasks`
|
|
49
|
-
| `todoist_get_task`
|
|
50
|
-
| `todoist_update_task`
|
|
51
|
-
| `todoist_delete_task`
|
|
52
|
-
| `todoist_complete_task`
|
|
53
|
-
| `todoist_reopen_task`
|
|
54
|
-
| `todoist_search_tasks`
|
|
46
|
+
| Tool | Description |
|
|
47
|
+
|---------------------------|-------------------------------------------------------------------------------------|
|
|
48
|
+
| `todoist_create_task` | Create tasks with full options (subtasks, labels, projects, sections, priorities). |
|
|
49
|
+
| `todoist_quick_add_task` | Natural language task creation using Todoist's Quick Add syntax. |
|
|
50
|
+
| `todoist_get_tasks` | Retrieve tasks with filtering (project, section, parent, label, IDs) and pagination. |
|
|
51
|
+
| `todoist_get_task` | Get a specific task by its ID, with detailed information. |
|
|
52
|
+
| `todoist_update_task` | Update task properties (content, description, due date, priority, labels). |
|
|
53
|
+
| `todoist_delete_task` | Delete task by ID. |
|
|
54
|
+
| `todoist_complete_task` | Mark task complete. |
|
|
55
|
+
| `todoist_reopen_task` | Reopen completed task. |
|
|
56
|
+
| `todoist_search_tasks` | Search tasks using Todoist's filter engine (e.g., `search: keyword`). |
|
|
57
|
+
| `todoist_move_task` | Move a task to a different project, section, or make it a subtask. |
|
|
55
58
|
|
|
56
59
|
### Project Operations (5 tools)
|
|
57
|
-
| Tool
|
|
58
|
-
|
|
59
|
-
| `todoist_get_projects`
|
|
60
|
-
| `todoist_get_project`
|
|
61
|
-
| `todoist_create_project`
|
|
62
|
-
| `todoist_update_project`
|
|
63
|
-
| `todoist_delete_project`
|
|
60
|
+
| Tool | Description |
|
|
61
|
+
|----------------------------|-------------------------------------------------------------------------------|
|
|
62
|
+
| `todoist_get_projects` | List all active projects with pagination support. |
|
|
63
|
+
| `todoist_get_project` | Get a specific project by its ID. |
|
|
64
|
+
| `todoist_create_project` | Create new project (name, color, favorite, view style, sub-projects). |
|
|
65
|
+
| `todoist_update_project` | Update project properties. |
|
|
66
|
+
| `todoist_delete_project` | Delete project by ID. |
|
|
64
67
|
|
|
65
68
|
### Section Operations (4 tools)
|
|
66
|
-
| Tool
|
|
67
|
-
|
|
68
|
-
| `todoist_get_sections`
|
|
69
|
-
| `todoist_create_section`
|
|
70
|
-
| `todoist_update_section`
|
|
71
|
-
| `todoist_delete_section`
|
|
69
|
+
| Tool | Description |
|
|
70
|
+
|----------------------------|-----------------------------------------------------------------|
|
|
71
|
+
| `todoist_get_sections` | List sections (all sections or project-specific). |
|
|
72
|
+
| `todoist_create_section` | Create section in project (name, project, ordering). |
|
|
73
|
+
| `todoist_update_section` | Update section name. |
|
|
74
|
+
| `todoist_delete_section` | Delete section by ID. |
|
|
75
|
+
|
|
76
|
+
### Label Operations (5 tools)
|
|
77
|
+
| Tool | Description |
|
|
78
|
+
|--------------------------|--------------------------------------------------------------------|
|
|
79
|
+
| `todoist_create_label` | Create a new label (name, color, favorite, order). |
|
|
80
|
+
| `todoist_get_label` | Get a specific label by its ID. |
|
|
81
|
+
| `todoist_get_labels` | List all labels with pagination support. |
|
|
82
|
+
| `todoist_update_label` | Update an existing label by its ID (name, color, favorite, order). |
|
|
83
|
+
| `todoist_delete_label` | Delete a label by its ID. |
|
|
72
84
|
|
|
73
85
|
## 🚀 Installation & Setup
|
|
74
86
|
|
|
@@ -101,7 +113,7 @@ Add to your `claude_desktop_config.json`:
|
|
|
101
113
|
"mcpServers": {
|
|
102
114
|
"todoist-mcp-server": {
|
|
103
115
|
"command": "node",
|
|
104
|
-
"args": ["/path/to/todoist-mcp-server-ext/dist/index.js"],
|
|
116
|
+
"args": ["/path/to/your/todoist-mcp-server-ext/dist/index.js"],
|
|
105
117
|
"env": {
|
|
106
118
|
"TODOIST_API_TOKEN": "your_api_token_here"
|
|
107
119
|
}
|
|
@@ -110,13 +122,13 @@ Add to your `claude_desktop_config.json`:
|
|
|
110
122
|
}
|
|
111
123
|
```
|
|
112
124
|
|
|
113
|
-
#### Option 2: Run via npm (
|
|
125
|
+
#### Option 2: Run via npm/npx (recommended for published version)
|
|
114
126
|
```json
|
|
115
127
|
{
|
|
116
128
|
"mcpServers": {
|
|
117
129
|
"todoist-mcp-server": {
|
|
118
130
|
"command": "npx",
|
|
119
|
-
"args": ["-y", "@kydycode/todoist-mcp-server-ext"],
|
|
131
|
+
"args": ["-y", "@kydycode/todoist-mcp-server-ext@latest"],
|
|
120
132
|
"env": {
|
|
121
133
|
"TODOIST_API_TOKEN": "your_api_token_here"
|
|
122
134
|
}
|
|
@@ -128,7 +140,7 @@ Add to your `claude_desktop_config.json`:
|
|
|
128
140
|
#### Option 3: Install globally first
|
|
129
141
|
```bash
|
|
130
142
|
# Install the extended version globally
|
|
131
|
-
npm install -g @kydycode/todoist-mcp-server-ext
|
|
143
|
+
npm install -g @kydycode/todoist-mcp-server-ext@latest
|
|
132
144
|
|
|
133
145
|
# Then use in Claude Desktop config
|
|
134
146
|
{
|
|
@@ -145,53 +157,40 @@ npm install -g @kydycode/todoist-mcp-server-ext
|
|
|
145
157
|
|
|
146
158
|
## 📖 Usage Examples
|
|
147
159
|
|
|
148
|
-
### 🎯 Advanced Task Creation
|
|
160
|
+
### 🎯 Advanced Task Creation & Management
|
|
149
161
|
```
|
|
150
|
-
"Create task 'Team Meeting
|
|
151
|
-
"Add
|
|
162
|
+
"Create task 'Team Meeting @Tomorrow #Work p1'"
|
|
163
|
+
"Add task 'Fix critical bug +KydyCode @DevProject L:Urgent L:Backend'"
|
|
152
164
|
"Quick add: 'Buy milk tomorrow at 2pm #shopping !p1'"
|
|
153
|
-
"
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
### 🔍 Task Management & Search
|
|
157
|
-
```
|
|
158
|
-
"Get all tasks in project 'Work'"
|
|
159
|
-
"Search for tasks containing 'meeting'"
|
|
160
|
-
"Show task details for specific task ID"
|
|
161
|
-
"Update task priority to urgent"
|
|
162
|
-
"Complete task and then reopen it"
|
|
165
|
+
"Move task with ID {task_id} to project {project_id}"
|
|
166
|
+
"Search tasks: search: API deployment"
|
|
163
167
|
```
|
|
164
168
|
|
|
165
|
-
### 🗂️ Project
|
|
169
|
+
### 🗂️ Project, Section, and Label Management
|
|
166
170
|
```
|
|
167
171
|
"List all my projects"
|
|
168
|
-
"Create project '
|
|
169
|
-
"
|
|
170
|
-
"
|
|
171
|
-
"
|
|
172
|
-
```
|
|
173
|
-
|
|
174
|
-
### 📋 Section Management
|
|
175
|
-
```
|
|
176
|
-
"Create section 'In Progress' in Development project"
|
|
177
|
-
"List all sections in Work project"
|
|
178
|
-
"Update section name to 'Completed'"
|
|
179
|
-
"Delete empty section"
|
|
172
|
+
"Create project 'Q2 Planning' color:blue favorite:true view:board"
|
|
173
|
+
"Get sections for project {project_id}"
|
|
174
|
+
"Create label 'HighPriority' color:red isFavorite:true"
|
|
175
|
+
"List all labels"
|
|
180
176
|
```
|
|
181
177
|
|
|
182
178
|
## 🆚 Extended vs Original Comparison
|
|
183
179
|
|
|
184
|
-
| Feature
|
|
185
|
-
|
|
186
|
-
| **
|
|
187
|
-
| **
|
|
188
|
-
| **
|
|
189
|
-
| **
|
|
190
|
-
| **
|
|
191
|
-
| **
|
|
192
|
-
| **
|
|
193
|
-
| **
|
|
194
|
-
| **
|
|
180
|
+
| Feature | Original | Extended Version (`@kydycode/todoist-mcp-server-ext`) |
|
|
181
|
+
|-----------------------------|--------------------------------------|-------------------------------------------------------|
|
|
182
|
+
| **Todoist SDK Version** | Older | ✅ `@doist/todoist-api-typescript@4.0.4` |
|
|
183
|
+
| **MCP SDK Compatibility** | Older version | ✅ Latest MCP SDK 0.5.0+ |
|
|
184
|
+
| **Error Handling** | Basic | ✅ Comprehensive with detailed messages |
|
|
185
|
+
| **TypeScript Support** | Limited | ✅ Full type safety |
|
|
186
|
+
| **Task Operations** | Search-based, limited features | ✅ 10 Tools: Direct ID-based, `moveTasks`, robust search, QuickAdd, full CRUD-like ops |
|
|
187
|
+
| **Project Management** | Limited | ✅ 5 Tools: Full CRUD operations, sub-projects, pagination |
|
|
188
|
+
| **Section Management** | Basic | ✅ 4 Tools: Complete section operations |
|
|
189
|
+
| **Label Management** | Not Available | ✅ 5 Tools: Full CRUD operations, pagination |
|
|
190
|
+
| **API Parameter Handling** | Inconsistent | ✅ Proper parameter validation |
|
|
191
|
+
| **Response Formatting** | Basic | ✅ Enhanced readability, more details |
|
|
192
|
+
| **Build System** | Issues | ✅ Clean compilation |
|
|
193
|
+
| **Search Functionality** | Basic local filter | ✅ Robust `getTasksByFilter` (Todoist engine) |
|
|
195
194
|
|
|
196
195
|
## 🔧 Development
|
|
197
196
|
|
|
@@ -199,9 +198,13 @@ npm install -g @kydycode/todoist-mcp-server-ext
|
|
|
199
198
|
```
|
|
200
199
|
src/
|
|
201
200
|
├── index.ts # Main server implementation with all tools
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
package.json # Dependencies and scripts
|
|
202
|
+
tsconfig.json # TypeScript configuration
|
|
203
|
+
README.md # This file
|
|
204
|
+
local-instructions.md # Personal publishing guide
|
|
205
|
+
LICENSE
|
|
206
|
+
.gitignore
|
|
207
|
+
dist/ # Compiled JavaScript output (after `npm run build`)
|
|
205
208
|
├── index.js
|
|
206
209
|
└── index.d.ts
|
|
207
210
|
```
|
|
@@ -215,14 +218,16 @@ npm install
|
|
|
215
218
|
npm run build
|
|
216
219
|
|
|
217
220
|
# Test the server (requires TODOIST_API_TOKEN)
|
|
218
|
-
|
|
221
|
+
# Example: Set token and pipe a list tools request
|
|
222
|
+
export TODOIST_API_TOKEN="your_actual_todoist_api_token"
|
|
223
|
+
echo '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}' | node dist/index.js
|
|
219
224
|
```
|
|
220
225
|
|
|
221
226
|
### Development Scripts
|
|
222
227
|
```bash
|
|
223
|
-
npm run build # Compile TypeScript
|
|
224
|
-
npm run watch # Watch for changes and rebuild
|
|
225
|
-
npm run prepare # Pre-publish build
|
|
228
|
+
npm run build # Compile TypeScript and make output executable
|
|
229
|
+
npm run watch # Watch for changes and rebuild (doesn't make output executable)
|
|
230
|
+
npm run prepare # Pre-publish build (runs build)
|
|
226
231
|
```
|
|
227
232
|
|
|
228
233
|
## 🤝 Contributing
|
|
@@ -255,6 +260,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
255
260
|
## 🔗 Related Links
|
|
256
261
|
|
|
257
262
|
- **Extended Repository**: [kydycode/todoist-mcp-server-ext](https://github.com/kydycode/todoist-mcp-server-ext)
|
|
263
|
+
- **NPM Package**: [`@kydycode/todoist-mcp-server-ext`](https://www.npmjs.com/package/@kydycode/todoist-mcp-server-ext)
|
|
258
264
|
- **Original Repository**: [abhiz123/todoist-mcp-server](https://github.com/abhiz123/todoist-mcp-server)
|
|
259
265
|
- [Todoist API Documentation](https://developer.todoist.com/)
|
|
260
266
|
- [Model Context Protocol](https://modelcontextprotocol.io/)
|
package/dist/index.js
CHANGED
|
@@ -209,6 +209,134 @@ const REOPEN_TASK_TOOL = {
|
|
|
209
209
|
required: ["taskId"]
|
|
210
210
|
}
|
|
211
211
|
};
|
|
212
|
+
const MOVE_TASK_TOOL = {
|
|
213
|
+
name: "todoist_move_task",
|
|
214
|
+
description: "Move a single task (and its subtasks, if any) to a different project, section, or make it a subtask of another task. Provide the taskId and exactly one of: projectId, sectionId, or parentId.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
taskId: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "The ID of the task to move."
|
|
221
|
+
},
|
|
222
|
+
projectId: {
|
|
223
|
+
type: "string",
|
|
224
|
+
description: "The ID of the destination project. (Optional, use only one of projectId, sectionId, parentId)"
|
|
225
|
+
},
|
|
226
|
+
sectionId: {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "The ID of the destination section. (Optional, use only one of projectId, sectionId, parentId)"
|
|
229
|
+
},
|
|
230
|
+
parentId: {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "The ID of the parent task to move this task under. (Optional, use only one of projectId, sectionId, parentId)"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
required: ["taskId"]
|
|
236
|
+
// Note: Validation for providing exactly one of projectId, sectionId, or parentId
|
|
237
|
+
// is handled in the isMoveTaskArgs type guard and the tool handler.
|
|
238
|
+
// A more complex JSON schema with oneOf could enforce this, but client support varies.
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
const BULK_MOVE_TASKS_TOOL = {
|
|
242
|
+
name: "todoist_bulk_move_tasks",
|
|
243
|
+
description: "Move multiple tasks (and their respective subtasks, if any; e.g., up to 10-20 parent tasks for best performance) to a different project, section, or make them subtasks of another task. Provide an array of taskIds and exactly one destination (projectId, sectionId, or parentId).",
|
|
244
|
+
inputSchema: {
|
|
245
|
+
type: "object",
|
|
246
|
+
properties: {
|
|
247
|
+
taskIds: {
|
|
248
|
+
type: "array",
|
|
249
|
+
items: { type: "string" },
|
|
250
|
+
description: "An array of task IDs to move.",
|
|
251
|
+
minItems: 1 // Ensure at least one task ID is provided
|
|
252
|
+
},
|
|
253
|
+
projectId: {
|
|
254
|
+
type: "string",
|
|
255
|
+
description: "The ID of the destination project. (Optional, use only one of projectId, sectionId, parentId)"
|
|
256
|
+
},
|
|
257
|
+
sectionId: {
|
|
258
|
+
type: "string",
|
|
259
|
+
description: "The ID of the destination section. (Optional, use only one of projectId, sectionId, parentId)"
|
|
260
|
+
},
|
|
261
|
+
parentId: {
|
|
262
|
+
type: "string",
|
|
263
|
+
description: "The ID of the parent task to move these tasks under. (Optional, use only one of projectId, sectionId, parentId)"
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
required: ["taskIds"]
|
|
267
|
+
// Note: Validation for providing exactly one of projectId, sectionId, or parentId
|
|
268
|
+
// is handled in the isBulkMoveTasksArgs type guard and the tool handler.
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
// Label Management Tools
|
|
272
|
+
const CREATE_LABEL_TOOL = {
|
|
273
|
+
name: "todoist_create_label",
|
|
274
|
+
description: "Create a new label.",
|
|
275
|
+
inputSchema: {
|
|
276
|
+
type: "object",
|
|
277
|
+
properties: {
|
|
278
|
+
name: { type: "string", description: "The name of the label." },
|
|
279
|
+
color: { type: "string", description: "Label color name or code (e.g., 'berry_red', '#FF0000') (optional)." },
|
|
280
|
+
isFavorite: { type: "boolean", description: "Whether the label should be a favorite (optional)." },
|
|
281
|
+
order: { type: "number", description: "The order of the label in the list (optional)." }
|
|
282
|
+
},
|
|
283
|
+
required: ["name"]
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const GET_LABEL_TOOL = {
|
|
287
|
+
name: "todoist_get_label",
|
|
288
|
+
description: "Get a specific label by its ID.",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
labelId: { type: "string", description: "The ID of the label to retrieve." }
|
|
293
|
+
},
|
|
294
|
+
required: ["labelId"]
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
const GET_LABELS_TOOL = {
|
|
298
|
+
name: "todoist_get_labels",
|
|
299
|
+
description: "Get all labels. Supports pagination.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
cursor: {
|
|
304
|
+
type: "string",
|
|
305
|
+
description: "Pagination cursor for next page (optional)."
|
|
306
|
+
},
|
|
307
|
+
limit: {
|
|
308
|
+
type: "number",
|
|
309
|
+
description: "Maximum number of labels to return (default: 50) (optional)."
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const UPDATE_LABEL_TOOL = {
|
|
315
|
+
name: "todoist_update_label",
|
|
316
|
+
description: "Update an existing label by its ID.",
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: "object",
|
|
319
|
+
properties: {
|
|
320
|
+
labelId: { type: "string", description: "The ID of the label to update." },
|
|
321
|
+
name: { type: "string", description: "New name for the label (optional)." },
|
|
322
|
+
color: { type: "string", description: "New color for the label (optional)." },
|
|
323
|
+
isFavorite: { type: "boolean", description: "New favorite status (optional)." },
|
|
324
|
+
order: { type: "number", description: "New order for the label (optional)." }
|
|
325
|
+
},
|
|
326
|
+
required: ["labelId"]
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
const DELETE_LABEL_TOOL = {
|
|
330
|
+
name: "todoist_delete_label",
|
|
331
|
+
description: "Delete a label by its ID.",
|
|
332
|
+
inputSchema: {
|
|
333
|
+
type: "object",
|
|
334
|
+
properties: {
|
|
335
|
+
labelId: { type: "string", description: "The ID of the label to delete." }
|
|
336
|
+
},
|
|
337
|
+
required: ["labelId"]
|
|
338
|
+
}
|
|
339
|
+
};
|
|
212
340
|
// Project Management Tools
|
|
213
341
|
const GET_PROJECTS_TOOL = {
|
|
214
342
|
name: "todoist_get_projects",
|
|
@@ -321,13 +449,21 @@ const DELETE_PROJECT_TOOL = {
|
|
|
321
449
|
// Section Management Tools
|
|
322
450
|
const GET_SECTIONS_TOOL = {
|
|
323
451
|
name: "todoist_get_sections",
|
|
324
|
-
description: "Get all sections or sections for a specific project",
|
|
452
|
+
description: "Get all sections, or sections for a specific project. Supports pagination.",
|
|
325
453
|
inputSchema: {
|
|
326
454
|
type: "object",
|
|
327
455
|
properties: {
|
|
328
456
|
projectId: {
|
|
329
457
|
type: "string",
|
|
330
|
-
description: "Filter sections by project ID (optional)"
|
|
458
|
+
description: "Filter sections by project ID (optional)."
|
|
459
|
+
},
|
|
460
|
+
cursor: {
|
|
461
|
+
type: "string",
|
|
462
|
+
description: "Pagination cursor for next page (optional)."
|
|
463
|
+
},
|
|
464
|
+
limit: {
|
|
465
|
+
type: "number",
|
|
466
|
+
description: "Maximum number of sections to return (default: 50) (optional)."
|
|
331
467
|
}
|
|
332
468
|
}
|
|
333
469
|
}
|
|
@@ -421,11 +557,38 @@ const server = new Server({
|
|
|
421
557
|
});
|
|
422
558
|
// Helper function to format task output
|
|
423
559
|
function formatTask(task) {
|
|
424
|
-
|
|
560
|
+
let taskDetails = `- ID: ${task.id}\n Content: ${task.content}`;
|
|
561
|
+
if (task.description)
|
|
562
|
+
taskDetails += `\n Description: ${task.description}`;
|
|
563
|
+
if (task.due)
|
|
564
|
+
taskDetails += `\n Due: ${task.due.string}`;
|
|
565
|
+
if (task.priority && task.priority > 1)
|
|
566
|
+
taskDetails += `\n Priority: ${task.priority}`;
|
|
567
|
+
if (task.labels && task.labels.length > 0)
|
|
568
|
+
taskDetails += `\n Labels: ${task.labels.join(', ')}`;
|
|
569
|
+
if (task.projectId)
|
|
570
|
+
taskDetails += `\n Project ID: ${task.projectId}`;
|
|
571
|
+
if (task.sectionId)
|
|
572
|
+
taskDetails += `\n Section ID: ${task.sectionId}`;
|
|
573
|
+
if (task.parentId)
|
|
574
|
+
taskDetails += `\n Parent ID: ${task.parentId}`;
|
|
575
|
+
if (task.url)
|
|
576
|
+
taskDetails += `\n URL: ${task.url}`;
|
|
577
|
+
if (task.commentCount > 0)
|
|
578
|
+
taskDetails += `\n Comments: ${task.commentCount}`;
|
|
579
|
+
if (task.createdAt)
|
|
580
|
+
taskDetails += `\n Created At: ${task.createdAt}`;
|
|
581
|
+
if (task.creatorId)
|
|
582
|
+
taskDetails += `\n Creator ID: ${task.creatorId}`;
|
|
583
|
+
return taskDetails;
|
|
425
584
|
}
|
|
426
585
|
// Helper function to format project output
|
|
427
586
|
function formatProject(project) {
|
|
428
|
-
return `- ${project.name}${project.color ? `\n Color: ${project.color}` : ''}${project.isFavorite ? `\n Favorite: Yes` : ''}${project.viewStyle ? `\n View: ${project.viewStyle}` : ''}${project.parentId ? `\n Parent: ${project.parentId}` : ''}`;
|
|
587
|
+
return `- ${project.name}${project.color ? `\n Color: ${project.color}` : ''}${project.isFavorite ? `\n Favorite: Yes` : ''}${project.viewStyle ? `\n View: ${project.viewStyle}` : ''}${project.parentId ? `\n Parent: ${project.parentId}` : ''}${project.id ? ` (ID: ${project.id})` : ''}`;
|
|
588
|
+
}
|
|
589
|
+
// Helper function to format label output
|
|
590
|
+
function formatLabel(label) {
|
|
591
|
+
return `- ${label.name} (ID: ${label.id})${label.color ? `\n Color: ${label.color}` : ''}${label.isFavorite ? `\n Favorite: Yes` : ''}${label.order ? `\n Order: ${label.order}` : ''}`;
|
|
429
592
|
}
|
|
430
593
|
// Type guards for arguments
|
|
431
594
|
function isCreateTaskArgs(args) {
|
|
@@ -456,6 +619,7 @@ function isUpdateTaskArgs(args) {
|
|
|
456
619
|
typeof args.taskId === "string");
|
|
457
620
|
}
|
|
458
621
|
function isProjectArgs(args) {
|
|
622
|
+
// Allows empty object or object with optional cursor/limit
|
|
459
623
|
return typeof args === "object" && args !== null;
|
|
460
624
|
}
|
|
461
625
|
function isProjectIdArgs(args) {
|
|
@@ -477,6 +641,7 @@ function isUpdateProjectArgs(args) {
|
|
|
477
641
|
typeof args.projectId === "string");
|
|
478
642
|
}
|
|
479
643
|
function isSectionArgs(args) {
|
|
644
|
+
// Allows empty object or object with optional projectId, cursor, limit
|
|
480
645
|
return typeof args === "object" && args !== null;
|
|
481
646
|
}
|
|
482
647
|
function isCreateSectionArgs(args) {
|
|
@@ -507,6 +672,54 @@ function isSearchTasksArgs(args) {
|
|
|
507
672
|
"query" in args &&
|
|
508
673
|
typeof args.query === "string");
|
|
509
674
|
}
|
|
675
|
+
function isMoveTaskArgs(args) {
|
|
676
|
+
if (typeof args !== 'object' || args === null || !('taskId' in args) || typeof args.taskId !== 'string') {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
const { projectId, sectionId, parentId } = args;
|
|
680
|
+
const destinations = [projectId, sectionId, parentId];
|
|
681
|
+
const providedDestinations = destinations.filter(dest => dest !== undefined && dest !== null && String(dest).trim() !== '');
|
|
682
|
+
// Exactly one destination must be provided and be a non-empty string
|
|
683
|
+
return providedDestinations.length === 1 &&
|
|
684
|
+
providedDestinations.every(dest => typeof dest === 'string');
|
|
685
|
+
}
|
|
686
|
+
function isBulkMoveTasksArgs(args) {
|
|
687
|
+
if (typeof args !== 'object' ||
|
|
688
|
+
args === null ||
|
|
689
|
+
!('taskIds' in args) ||
|
|
690
|
+
!Array.isArray(args.taskIds) ||
|
|
691
|
+
args.taskIds.length === 0 ||
|
|
692
|
+
!args.taskIds.every((id) => typeof id === 'string')) {
|
|
693
|
+
return false;
|
|
694
|
+
}
|
|
695
|
+
const { projectId, sectionId, parentId } = args;
|
|
696
|
+
const destinations = [projectId, sectionId, parentId];
|
|
697
|
+
const providedDestinations = destinations.filter(dest => dest !== undefined && dest !== null && String(dest).trim() !== '');
|
|
698
|
+
return providedDestinations.length === 1 &&
|
|
699
|
+
providedDestinations.every(dest => typeof dest === 'string');
|
|
700
|
+
}
|
|
701
|
+
function isCreateLabelArgs(args) {
|
|
702
|
+
return (typeof args === "object" &&
|
|
703
|
+
args !== null &&
|
|
704
|
+
"name" in args &&
|
|
705
|
+
typeof args.name === "string");
|
|
706
|
+
}
|
|
707
|
+
function isLabelIdArgs(args) {
|
|
708
|
+
return (typeof args === "object" &&
|
|
709
|
+
args !== null &&
|
|
710
|
+
"labelId" in args &&
|
|
711
|
+
typeof args.labelId === "string");
|
|
712
|
+
}
|
|
713
|
+
// Type guard for get_labels which takes no arguments in this SDK version
|
|
714
|
+
function isGetLabelsArgs(args) {
|
|
715
|
+
return typeof args === "object" && args !== null;
|
|
716
|
+
}
|
|
717
|
+
function isUpdateLabelArgs(args) {
|
|
718
|
+
return (typeof args === "object" &&
|
|
719
|
+
args !== null &&
|
|
720
|
+
"labelId" in args &&
|
|
721
|
+
typeof args.labelId === "string");
|
|
722
|
+
}
|
|
510
723
|
// Tool handlers
|
|
511
724
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
512
725
|
tools: [
|
|
@@ -520,6 +733,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
520
733
|
COMPLETE_TASK_TOOL,
|
|
521
734
|
REOPEN_TASK_TOOL,
|
|
522
735
|
SEARCH_TASKS_TOOL,
|
|
736
|
+
MOVE_TASK_TOOL,
|
|
737
|
+
BULK_MOVE_TASKS_TOOL,
|
|
523
738
|
// Project tools
|
|
524
739
|
GET_PROJECTS_TOOL,
|
|
525
740
|
GET_PROJECT_TOOL,
|
|
@@ -531,6 +746,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
531
746
|
CREATE_SECTION_TOOL,
|
|
532
747
|
UPDATE_SECTION_TOOL,
|
|
533
748
|
DELETE_SECTION_TOOL,
|
|
749
|
+
// Label tools
|
|
750
|
+
CREATE_LABEL_TOOL,
|
|
751
|
+
GET_LABEL_TOOL,
|
|
752
|
+
GET_LABELS_TOOL,
|
|
753
|
+
UPDATE_LABEL_TOOL,
|
|
754
|
+
DELETE_LABEL_TOOL,
|
|
534
755
|
],
|
|
535
756
|
}));
|
|
536
757
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -707,26 +928,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
707
928
|
if (!isSearchTasksArgs(args)) {
|
|
708
929
|
throw new Error("Invalid arguments for todoist_search_tasks");
|
|
709
930
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
931
|
+
// Prepare arguments for getTasksByFilter
|
|
932
|
+
// Prepend "search: " to the query for more robust keyword searching with Todoist API
|
|
933
|
+
const searchQuery = args.query.startsWith("search:") ? args.query : `search: ${args.query}`;
|
|
934
|
+
const filterArgs = { query: searchQuery };
|
|
935
|
+
if (args.limit)
|
|
936
|
+
filterArgs.limit = args.limit;
|
|
937
|
+
// Note: args.projectId is not directly used by getTasksByFilter unless incorporated into the query string.
|
|
938
|
+
// For example: `search: ${args.query} & #ProjectName` or `search: ${args.query} & ##ProjectID`
|
|
939
|
+
const tasksResponse = await todoistClient.getTasksByFilter(filterArgs);
|
|
940
|
+
const matchingTasksData = tasksResponse.results || [];
|
|
941
|
+
if (matchingTasksData.length === 0) {
|
|
717
942
|
return {
|
|
718
943
|
content: [{
|
|
719
944
|
type: "text",
|
|
720
|
-
text: `No tasks found matching "${args.query}"`
|
|
945
|
+
text: `No tasks found matching the filter query "${args.query}"`
|
|
721
946
|
}],
|
|
722
947
|
isError: false,
|
|
723
948
|
};
|
|
724
949
|
}
|
|
725
|
-
|
|
950
|
+
// Asynchronously format tasks and fetch project names if necessary
|
|
951
|
+
const formattedTaskList = await Promise.all(matchingTasksData.map(async (task) => {
|
|
952
|
+
let taskDisplay = formatTask(task); // formatTask now includes Project ID
|
|
953
|
+
if (task.projectId) {
|
|
954
|
+
try {
|
|
955
|
+
const project = await todoistClient.getProject(task.projectId);
|
|
956
|
+
taskDisplay += `\n Project Name: ${project.name}`;
|
|
957
|
+
}
|
|
958
|
+
catch (projectError) {
|
|
959
|
+
// Silently ignore project fetch errors for search, or log them
|
|
960
|
+
// taskDisplay += `\n Project Name: (Error fetching project details)`;
|
|
961
|
+
console.error(`Error fetching project ${task.projectId} for search result: ${projectError.message}`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return taskDisplay;
|
|
965
|
+
}));
|
|
966
|
+
const taskListString = formattedTaskList.join('\n\n');
|
|
967
|
+
const nextCursorMessage = tasksResponse.nextCursor ? `\n\nNext cursor for more results: ${tasksResponse.nextCursor}` : '';
|
|
726
968
|
return {
|
|
727
969
|
content: [{
|
|
728
970
|
type: "text",
|
|
729
|
-
text: `Found ${
|
|
971
|
+
text: `Found ${matchingTasksData.length} task(s) matching "${args.query}":\n\n${taskListString}${nextCursorMessage}`
|
|
730
972
|
}],
|
|
731
973
|
isError: false,
|
|
732
974
|
};
|
|
@@ -741,16 +983,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
741
983
|
params.cursor = args.cursor;
|
|
742
984
|
if (args.limit)
|
|
743
985
|
params.limit = args.limit;
|
|
744
|
-
|
|
745
|
-
const
|
|
746
|
-
|
|
747
|
-
const projectList = Array.isArray(projects)
|
|
748
|
-
? projects.map(formatProject).join('\n\n')
|
|
749
|
-
: 'No projects found';
|
|
986
|
+
const projectsResponse = await todoistClient.getProjects(params);
|
|
987
|
+
const projectList = projectsResponse.results?.map(formatProject).join('\n\n') || 'No projects found';
|
|
988
|
+
const nextCursor = projectsResponse.nextCursor ? `\n\nNext cursor: ${projectsResponse.nextCursor}` : '';
|
|
750
989
|
return {
|
|
751
990
|
content: [{
|
|
752
991
|
type: "text",
|
|
753
|
-
text: `Projects:\n${projectList}`
|
|
992
|
+
text: `Projects:\n${projectList}${nextCursor}`
|
|
754
993
|
}],
|
|
755
994
|
isError: false,
|
|
756
995
|
};
|
|
@@ -833,14 +1072,17 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
833
1072
|
const params = {};
|
|
834
1073
|
if (args.projectId)
|
|
835
1074
|
params.projectId = args.projectId;
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1075
|
+
if (args.cursor)
|
|
1076
|
+
params.cursor = args.cursor;
|
|
1077
|
+
if (args.limit)
|
|
1078
|
+
params.limit = args.limit;
|
|
1079
|
+
const sectionsResponse = await todoistClient.getSections(params);
|
|
1080
|
+
const sectionList = sectionsResponse.results?.map((section) => `- ${section.name} (ID: ${section.id}, Project ID: ${section.projectId})`).join('\n') || 'No sections found';
|
|
1081
|
+
const nextCursorMessage = sectionsResponse.nextCursor ? `\n\nNext cursor for more sections: ${sectionsResponse.nextCursor}` : '';
|
|
840
1082
|
return {
|
|
841
1083
|
content: [{
|
|
842
1084
|
type: "text",
|
|
843
|
-
text: `Sections:\n${sectionList
|
|
1085
|
+
text: `Sections:\n${sectionList}${nextCursorMessage}`
|
|
844
1086
|
}],
|
|
845
1087
|
isError: false,
|
|
846
1088
|
};
|
|
@@ -890,6 +1132,197 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
890
1132
|
isError: false,
|
|
891
1133
|
};
|
|
892
1134
|
}
|
|
1135
|
+
// Label operations
|
|
1136
|
+
if (name === "todoist_create_label") {
|
|
1137
|
+
if (!isCreateLabelArgs(args)) {
|
|
1138
|
+
return { content: [{ type: "text", text: "Invalid arguments for create_label" }], isError: true };
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
const label = await todoistClient.addLabel(args);
|
|
1142
|
+
return {
|
|
1143
|
+
content: [{ type: "text", text: `Label created:\n${formatLabel(label)}` }],
|
|
1144
|
+
isError: false
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
catch (error) {
|
|
1148
|
+
return { content: [{ type: "text", text: `Error creating label: ${error.message}` }], isError: true };
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (name === "todoist_get_label") {
|
|
1152
|
+
if (!isLabelIdArgs(args)) {
|
|
1153
|
+
return { content: [{ type: "text", text: "Invalid arguments for get_label" }], isError: true };
|
|
1154
|
+
}
|
|
1155
|
+
try {
|
|
1156
|
+
const label = await todoistClient.getLabel(args.labelId);
|
|
1157
|
+
return {
|
|
1158
|
+
content: [{ type: "text", text: `Label details:\n${formatLabel(label)}` }],
|
|
1159
|
+
isError: false
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
return { content: [{ type: "text", text: `Error getting label: ${error.message}` }], isError: true };
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
if (name === "todoist_get_labels") {
|
|
1167
|
+
if (!isGetLabelsArgs(args)) {
|
|
1168
|
+
return { content: [{ type: "text", text: "Invalid arguments for get_labels. This tool takes an optional cursor and limit." }], isError: true };
|
|
1169
|
+
}
|
|
1170
|
+
try {
|
|
1171
|
+
const params = {};
|
|
1172
|
+
if (args.cursor)
|
|
1173
|
+
params.cursor = args.cursor;
|
|
1174
|
+
if (args.limit)
|
|
1175
|
+
params.limit = args.limit;
|
|
1176
|
+
const labelsResponse = await todoistClient.getLabels(params);
|
|
1177
|
+
const labelList = labelsResponse.results?.map(formatLabel).join('\n\n') || 'No labels found';
|
|
1178
|
+
const nextCursor = labelsResponse.nextCursor ? `\n\nNext cursor for more labels: ${labelsResponse.nextCursor}` : '';
|
|
1179
|
+
return {
|
|
1180
|
+
content: [{
|
|
1181
|
+
type: "text",
|
|
1182
|
+
text: `Labels:\n${labelList}${nextCursor}`
|
|
1183
|
+
}],
|
|
1184
|
+
isError: false
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
catch (error) {
|
|
1188
|
+
return { content: [{ type: "text", text: `Error getting labels: ${error.message}` }], isError: true };
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
if (name === "todoist_update_label") {
|
|
1192
|
+
if (!isUpdateLabelArgs(args)) {
|
|
1193
|
+
return { content: [{ type: "text", text: "Invalid arguments for update_label" }], isError: true };
|
|
1194
|
+
}
|
|
1195
|
+
try {
|
|
1196
|
+
const { labelId, ...updateArgs } = args;
|
|
1197
|
+
const updatedLabel = await todoistClient.updateLabel(labelId, updateArgs);
|
|
1198
|
+
return {
|
|
1199
|
+
content: [{ type: "text", text: `Label updated:\n${formatLabel(updatedLabel)}` }],
|
|
1200
|
+
isError: false
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
catch (error) {
|
|
1204
|
+
return { content: [{ type: "text", text: `Error updating label: ${error.message}` }], isError: true };
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (name === "todoist_delete_label") {
|
|
1208
|
+
if (!isLabelIdArgs(args)) {
|
|
1209
|
+
return { content: [{ type: "text", text: "Invalid arguments for delete_label" }], isError: true };
|
|
1210
|
+
}
|
|
1211
|
+
try {
|
|
1212
|
+
await todoistClient.deleteLabel(args.labelId);
|
|
1213
|
+
return {
|
|
1214
|
+
content: [{ type: "text", text: `Label ${args.labelId} deleted.` }],
|
|
1215
|
+
isError: false
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
catch (error) {
|
|
1219
|
+
return { content: [{ type: "text", text: `Error deleting label: ${error.message}` }], isError: true };
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
// Move task operations
|
|
1223
|
+
if (name === "todoist_move_task") {
|
|
1224
|
+
if (!isMoveTaskArgs(args)) {
|
|
1225
|
+
return { content: [{ type: "text", text: "Invalid arguments for move_task. Provide taskId and exactly one of: projectId, sectionId, or parentId (must be a non-empty string)." }], isError: true };
|
|
1226
|
+
}
|
|
1227
|
+
try {
|
|
1228
|
+
const moveArgs = {};
|
|
1229
|
+
if (args.projectId)
|
|
1230
|
+
moveArgs.projectId = args.projectId;
|
|
1231
|
+
else if (args.sectionId)
|
|
1232
|
+
moveArgs.sectionId = args.sectionId;
|
|
1233
|
+
else if (args.parentId)
|
|
1234
|
+
moveArgs.parentId = args.parentId;
|
|
1235
|
+
// Use moveTasks from SDK v4+
|
|
1236
|
+
await todoistClient.moveTasks([args.taskId], moveArgs); // Cast to any for MoveTaskArgs as it expects RequireExactlyOne
|
|
1237
|
+
const movedTask = await todoistClient.getTask(args.taskId);
|
|
1238
|
+
return {
|
|
1239
|
+
content: [{
|
|
1240
|
+
type: "text",
|
|
1241
|
+
text: `Task ${args.taskId} moved successfully.\nNew details:\n${formatTask(movedTask)}`
|
|
1242
|
+
}],
|
|
1243
|
+
isError: false
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
catch (error) {
|
|
1247
|
+
return { content: [{ type: "text", text: `Error moving task: ${error.message}` }], isError: true };
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (name === "todoist_bulk_move_tasks") {
|
|
1251
|
+
if (!isBulkMoveTasksArgs(args)) {
|
|
1252
|
+
return { content: [{ type: "text", text: "Invalid arguments for bulk_move_tasks. Provide a non-empty array of taskIds and exactly one of: projectId, sectionId, or parentId (must be a non-empty string)." }], isError: true };
|
|
1253
|
+
}
|
|
1254
|
+
try {
|
|
1255
|
+
const moveArgs = {};
|
|
1256
|
+
if (args.projectId)
|
|
1257
|
+
moveArgs.projectId = args.projectId;
|
|
1258
|
+
else if (args.sectionId)
|
|
1259
|
+
moveArgs.sectionId = args.sectionId;
|
|
1260
|
+
else if (args.parentId)
|
|
1261
|
+
moveArgs.parentId = args.parentId;
|
|
1262
|
+
console.error(`[DEBUG] todoist_bulk_move_tasks: Attempting to move ${args.taskIds.length} task(s) individually. Destination args: ${JSON.stringify(moveArgs)}`);
|
|
1263
|
+
const results = {
|
|
1264
|
+
succeeded: [],
|
|
1265
|
+
failed: [],
|
|
1266
|
+
};
|
|
1267
|
+
for (const taskId of args.taskIds) {
|
|
1268
|
+
try {
|
|
1269
|
+
console.error(`[DEBUG] Moving task ${taskId} to: ${JSON.stringify(moveArgs)}`);
|
|
1270
|
+
const individualMoveResult = await todoistClient.moveTasks([taskId], moveArgs);
|
|
1271
|
+
// Check if the API returned the task and if its properties reflect the move
|
|
1272
|
+
// For simplicity, we assume if no error is thrown, it was accepted by the API.
|
|
1273
|
+
// A more robust check would be to fetch the task again and verify its sectionId/projectId.
|
|
1274
|
+
if (individualMoveResult && individualMoveResult.length > 0 && individualMoveResult[0].id === taskId) {
|
|
1275
|
+
console.error(`[DEBUG] Task ${taskId} processed by API. Result: ${JSON.stringify(individualMoveResult[0])}`);
|
|
1276
|
+
// Further check if sectionId or projectId in individualMoveResult[0] matches moveArgs
|
|
1277
|
+
const movedTaskDetails = individualMoveResult[0];
|
|
1278
|
+
let successfulMove = false;
|
|
1279
|
+
if (moveArgs.sectionId && movedTaskDetails.sectionId === moveArgs.sectionId)
|
|
1280
|
+
successfulMove = true;
|
|
1281
|
+
else if (moveArgs.projectId && movedTaskDetails.projectId === moveArgs.projectId)
|
|
1282
|
+
successfulMove = true;
|
|
1283
|
+
else if (moveArgs.parentId && movedTaskDetails.parentId === moveArgs.parentId)
|
|
1284
|
+
successfulMove = true;
|
|
1285
|
+
// If the API doesn't reflect the change immediately in the returned object, we might still count it as succeeded based on no error.
|
|
1286
|
+
// For now, we count as success if API call didn't throw and returned our task.
|
|
1287
|
+
if (successfulMove) {
|
|
1288
|
+
results.succeeded.push(taskId);
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
// This case means API processed it but didn't reflect the change in the returned object, or it was already there.
|
|
1292
|
+
// Could be a race condition or API behavior. We'll count it as attempted but not fully confirmed by response.
|
|
1293
|
+
console.warn(`[DEBUG] Task ${taskId} processed, but move not immediately confirmed in API response object. Counting as succeeded based on no error.`);
|
|
1294
|
+
results.succeeded.push(taskId); // Tentatively count as success
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
else {
|
|
1298
|
+
// API call succeeded but didn't return our task, or returned empty array
|
|
1299
|
+
console.warn(`[DEBUG] Task ${taskId} move API call succeeded but task not found in response or empty response.`);
|
|
1300
|
+
results.succeeded.push(taskId); // Tentatively count as success if API didn't error
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
catch (taskError) {
|
|
1304
|
+
console.error(`[DEBUG] Failed to move task ${taskId}: ${taskError.message}`);
|
|
1305
|
+
results.failed.push({ id: taskId, error: taskError.message });
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
let summaryMessage = `Bulk move attempt complete for ${args.taskIds.length} task(s). `;
|
|
1309
|
+
summaryMessage += `Succeeded: ${results.succeeded.length}. `;
|
|
1310
|
+
if (results.succeeded.length > 0)
|
|
1311
|
+
summaryMessage += `Moved IDs: ${results.succeeded.join(", ")}. `;
|
|
1312
|
+
summaryMessage += `Failed: ${results.failed.length}.`;
|
|
1313
|
+
if (results.failed.length > 0) {
|
|
1314
|
+
summaryMessage += ` Failed IDs: ${results.failed.map(f => `${f.id} (${f.error})`).join("; ")}`;
|
|
1315
|
+
}
|
|
1316
|
+
return {
|
|
1317
|
+
content: [{ type: "text", text: summaryMessage }],
|
|
1318
|
+
isError: results.failed.length > 0 && results.succeeded.length === 0, // Overall error if all fails
|
|
1319
|
+
};
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
console.error(`[DEBUG] todoist_bulk_move_tasks: Outer error caught: ${error.message}`, error);
|
|
1323
|
+
return { content: [{ type: "text", text: `Error in bulk moving tasks: ${error.message}` }], isError: true };
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
893
1326
|
return {
|
|
894
1327
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
895
1328
|
isError: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kydycode/todoist-mcp-server-ext",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Extended MCP server for Todoist API integration with enhanced features and improved compatibility",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://github.com/kydycode/todoist-mcp-server-ext#readme",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@doist/todoist-api-typescript": "^
|
|
40
|
+
"@doist/todoist-api-typescript": "^4.0.4",
|
|
41
41
|
"@modelcontextprotocol/sdk": "0.5.0",
|
|
42
42
|
"zod": "^3.22.0"
|
|
43
43
|
},
|
|
@@ -46,4 +46,4 @@
|
|
|
46
46
|
"shx": "^0.3.4",
|
|
47
47
|
"typescript": "^5.7.2"
|
|
48
48
|
}
|
|
49
|
-
}
|
|
49
|
+
}
|