@kydycode/todoist-mcp-server-ext 0.3.0 → 0.5.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 +47 -5
- package/dist/index.js +439 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,12 +15,13 @@ A comprehensive MCP (Model Context Protocol) server implementation that provides
|
|
|
15
15
|
* **Optimized API Usage**: Efficient use of Todoist API, including `getTasksByFilter` for robust search and `moveTasks` for semantic task movement.
|
|
16
16
|
* **Better Response Formatting**: Enhanced task, project, and label formatting for better readability, including project names in search results.
|
|
17
17
|
|
|
18
|
-
### ✨ **Enhanced Task Management (
|
|
18
|
+
### ✨ **Enhanced Task Management (11 Tools)**
|
|
19
19
|
* **Direct ID-based Operations**: Efficient task operations using task IDs.
|
|
20
20
|
* **Comprehensive Task Creation**: Support for subtasks, labels, projects, sections, priorities.
|
|
21
21
|
* **Quick Add Integration**: Natural language task creation using Todoist's Quick Add.
|
|
22
22
|
* **Advanced Task Search**: Robust keyword search using Todoist's filter engine (`search: your query`).
|
|
23
23
|
* **Task Movement Capabilities**: Move tasks between projects, sections, or make them subtasks.
|
|
24
|
+
* **Bulk Task Operations**: Move multiple tasks with subtasks in a single operation.
|
|
24
25
|
* **Task State Management**: Complete, reopen, and manage task lifecycle.
|
|
25
26
|
* **Detailed Task Output**: Search and get-task operations return more task details.
|
|
26
27
|
|
|
@@ -40,9 +41,21 @@ A comprehensive MCP (Model Context Protocol) server implementation that provides
|
|
|
40
41
|
* **Label Customization**: Set names, colors, favorites, order.
|
|
41
42
|
* **Paginated Label Listing**: Efficiently retrieve all labels.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
### 💬 **Comment Management (5 Tools)**
|
|
45
|
+
* **Complete Comment CRUD**: Create, read, update, delete comments on tasks and projects.
|
|
46
|
+
* **Attachment Support**: Add file attachments to comments with metadata.
|
|
47
|
+
* **Flexible Targeting**: Comments can be attached to either tasks or projects.
|
|
48
|
+
* **Paginated Comment Retrieval**: Efficiently browse through comment threads.
|
|
44
49
|
|
|
45
|
-
###
|
|
50
|
+
### ✅ **Completed Tasks History (1 Tool)** 🆕
|
|
51
|
+
* **Sync API Integration**: Access completed task history via Todoist Sync API.
|
|
52
|
+
* **Flexible Filtering**: Filter by project, date range (since/until).
|
|
53
|
+
* **Pagination Support**: Retrieve large histories with limit/offset.
|
|
54
|
+
* **Rich Metadata**: Shows project/section names, note counts, completion timestamps.
|
|
55
|
+
|
|
56
|
+
## 🛠️ Available Tools (Total 31)
|
|
57
|
+
|
|
58
|
+
### Task Operations (11 tools)
|
|
46
59
|
| Tool | Description |
|
|
47
60
|
|---------------------------|-------------------------------------------------------------------------------------|
|
|
48
61
|
| `todoist_create_task` | Create tasks with full options (subtasks, labels, projects, sections, priorities). |
|
|
@@ -55,6 +68,7 @@ A comprehensive MCP (Model Context Protocol) server implementation that provides
|
|
|
55
68
|
| `todoist_reopen_task` | Reopen completed task. |
|
|
56
69
|
| `todoist_search_tasks` | Search tasks using Todoist's filter engine (e.g., `search: keyword`). |
|
|
57
70
|
| `todoist_move_task` | Move a task to a different project, section, or make it a subtask. |
|
|
71
|
+
| `todoist_bulk_move_tasks` | Move multiple tasks with their subtasks to a project, section, or parent task. |
|
|
58
72
|
|
|
59
73
|
### Project Operations (5 tools)
|
|
60
74
|
| Tool | Description |
|
|
@@ -82,6 +96,20 @@ A comprehensive MCP (Model Context Protocol) server implementation that provides
|
|
|
82
96
|
| `todoist_update_label` | Update an existing label by its ID (name, color, favorite, order). |
|
|
83
97
|
| `todoist_delete_label` | Delete a label by its ID. |
|
|
84
98
|
|
|
99
|
+
### Comment Operations (5 tools)
|
|
100
|
+
| Tool | Description |
|
|
101
|
+
|----------------------------|-----------------------------------------------------------------|
|
|
102
|
+
| `todoist_create_comment` | Create a new comment on a task or project (with attachments). |
|
|
103
|
+
| `todoist_get_comment` | Get a specific comment by its ID. |
|
|
104
|
+
| `todoist_get_comments` | Get comments for a task or project with pagination support. |
|
|
105
|
+
| `todoist_update_comment` | Update an existing comment by its ID. |
|
|
106
|
+
| `todoist_delete_comment` | Delete a comment by its ID. |
|
|
107
|
+
|
|
108
|
+
### Completed Tasks Operations (1 tool) 🆕
|
|
109
|
+
| Tool | Description |
|
|
110
|
+
|--------------------------------|-----------------------------------------------------------------|
|
|
111
|
+
| `todoist_get_completed_tasks` | Get completed tasks history with filtering by project, date range (since/until), and pagination. Uses Todoist Sync API. |
|
|
112
|
+
|
|
85
113
|
## 🚀 Installation & Setup
|
|
86
114
|
|
|
87
115
|
### Local Development Setup
|
|
@@ -166,13 +194,25 @@ npm install -g @kydycode/todoist-mcp-server-ext@latest
|
|
|
166
194
|
"Search tasks: search: API deployment"
|
|
167
195
|
```
|
|
168
196
|
|
|
169
|
-
### 🗂️ Project, Section, and
|
|
197
|
+
### 🗂️ Project, Section, Label, and Comment Management
|
|
170
198
|
```
|
|
171
199
|
"List all my projects"
|
|
172
200
|
"Create project 'Q2 Planning' color:blue favorite:true view:board"
|
|
173
201
|
"Get sections for project {project_id}"
|
|
174
202
|
"Create label 'HighPriority' color:red isFavorite:true"
|
|
175
203
|
"List all labels"
|
|
204
|
+
"Add comment 'Great progress on this task!' to task {task_id}"
|
|
205
|
+
"Get all comments for project {project_id}"
|
|
206
|
+
"Update comment {comment_id} with new content"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### ✅ Completed Tasks History 🆕
|
|
210
|
+
```
|
|
211
|
+
"Show my completed tasks"
|
|
212
|
+
"Get completed tasks from project {project_id}"
|
|
213
|
+
"Show tasks completed since 2024-01-01"
|
|
214
|
+
"Get completed tasks between 2024-01-01 and 2024-01-31"
|
|
215
|
+
"Show last 50 completed tasks"
|
|
176
216
|
```
|
|
177
217
|
|
|
178
218
|
## 🆚 Extended vs Original Comparison
|
|
@@ -183,10 +223,12 @@ npm install -g @kydycode/todoist-mcp-server-ext@latest
|
|
|
183
223
|
| **MCP SDK Compatibility** | Older version | ✅ Latest MCP SDK 0.5.0+ |
|
|
184
224
|
| **Error Handling** | Basic | ✅ Comprehensive with detailed messages |
|
|
185
225
|
| **TypeScript Support** | Limited | ✅ Full type safety |
|
|
186
|
-
| **Task Operations** | Search-based, limited features | ✅
|
|
226
|
+
| **Task Operations** | Search-based, limited features | ✅ 11 Tools: Direct ID-based, `moveTasks`, bulk move, robust search, QuickAdd, full CRUD-like ops |
|
|
187
227
|
| **Project Management** | Limited | ✅ 5 Tools: Full CRUD operations, sub-projects, pagination |
|
|
188
228
|
| **Section Management** | Basic | ✅ 4 Tools: Complete section operations |
|
|
189
229
|
| **Label Management** | Not Available | ✅ 5 Tools: Full CRUD operations, pagination |
|
|
230
|
+
| **Comment Management** | Not Available | ✅ 5 Tools: Full CRUD operations, attachments, pagination |
|
|
231
|
+
| **Completed Tasks History** | Not Available | ✅ 1 Tool: Sync API integration, date filtering, pagination |
|
|
190
232
|
| **API Parameter Handling** | Inconsistent | ✅ Proper parameter validation |
|
|
191
233
|
| **Response Formatting** | Basic | ✅ Enhanced readability, more details |
|
|
192
234
|
| **Build System** | Issues | ✅ Clean compilation |
|
package/dist/index.js
CHANGED
|
@@ -546,10 +546,147 @@ const SEARCH_TASKS_TOOL = {
|
|
|
546
546
|
required: ["query"]
|
|
547
547
|
}
|
|
548
548
|
};
|
|
549
|
+
// Comment Management Tools
|
|
550
|
+
const CREATE_COMMENT_TOOL = {
|
|
551
|
+
name: "todoist_create_comment",
|
|
552
|
+
description: "Create a new comment on a task or project",
|
|
553
|
+
inputSchema: {
|
|
554
|
+
type: "object",
|
|
555
|
+
properties: {
|
|
556
|
+
content: {
|
|
557
|
+
type: "string",
|
|
558
|
+
description: "The content/text of the comment"
|
|
559
|
+
},
|
|
560
|
+
taskId: {
|
|
561
|
+
type: "string",
|
|
562
|
+
description: "Task ID to add comment to (provide either taskId or projectId, not both)"
|
|
563
|
+
},
|
|
564
|
+
projectId: {
|
|
565
|
+
type: "string",
|
|
566
|
+
description: "Project ID to add comment to (provide either taskId or projectId, not both)"
|
|
567
|
+
},
|
|
568
|
+
attachment: {
|
|
569
|
+
type: "object",
|
|
570
|
+
description: "Optional file attachment (optional)",
|
|
571
|
+
properties: {
|
|
572
|
+
fileName: { type: "string" },
|
|
573
|
+
fileType: { type: "string" },
|
|
574
|
+
fileUrl: { type: "string" },
|
|
575
|
+
resourceType: { type: "string" }
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
required: ["content"]
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const GET_COMMENT_TOOL = {
|
|
583
|
+
name: "todoist_get_comment",
|
|
584
|
+
description: "Get a specific comment by its ID",
|
|
585
|
+
inputSchema: {
|
|
586
|
+
type: "object",
|
|
587
|
+
properties: {
|
|
588
|
+
commentId: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "The ID of the comment to retrieve"
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
required: ["commentId"]
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
const GET_COMMENTS_TOOL = {
|
|
597
|
+
name: "todoist_get_comments",
|
|
598
|
+
description: "Get comments for a task or project with pagination support",
|
|
599
|
+
inputSchema: {
|
|
600
|
+
type: "object",
|
|
601
|
+
properties: {
|
|
602
|
+
taskId: {
|
|
603
|
+
type: "string",
|
|
604
|
+
description: "Task ID to get comments for (provide either taskId or projectId, not both)"
|
|
605
|
+
},
|
|
606
|
+
projectId: {
|
|
607
|
+
type: "string",
|
|
608
|
+
description: "Project ID to get comments for (provide either taskId or projectId, not both)"
|
|
609
|
+
},
|
|
610
|
+
cursor: {
|
|
611
|
+
type: "string",
|
|
612
|
+
description: "Pagination cursor for next page (optional)"
|
|
613
|
+
},
|
|
614
|
+
limit: {
|
|
615
|
+
type: "number",
|
|
616
|
+
description: "Maximum number of comments to return (optional)"
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
const UPDATE_COMMENT_TOOL = {
|
|
622
|
+
name: "todoist_update_comment",
|
|
623
|
+
description: "Update an existing comment by its ID",
|
|
624
|
+
inputSchema: {
|
|
625
|
+
type: "object",
|
|
626
|
+
properties: {
|
|
627
|
+
commentId: {
|
|
628
|
+
type: "string",
|
|
629
|
+
description: "The ID of the comment to update"
|
|
630
|
+
},
|
|
631
|
+
content: {
|
|
632
|
+
type: "string",
|
|
633
|
+
description: "New content/text for the comment"
|
|
634
|
+
}
|
|
635
|
+
},
|
|
636
|
+
required: ["commentId", "content"]
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
const DELETE_COMMENT_TOOL = {
|
|
640
|
+
name: "todoist_delete_comment",
|
|
641
|
+
description: "Delete a comment by its ID",
|
|
642
|
+
inputSchema: {
|
|
643
|
+
type: "object",
|
|
644
|
+
properties: {
|
|
645
|
+
commentId: {
|
|
646
|
+
type: "string",
|
|
647
|
+
description: "The ID of the comment to delete"
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
required: ["commentId"]
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
const GET_COMPLETED_TASKS_TOOL = {
|
|
654
|
+
name: "todoist_get_completed_tasks",
|
|
655
|
+
description: "Get completed tasks from Todoist with flexible filtering and pagination support. Uses the Todoist Sync API to retrieve task completion history.",
|
|
656
|
+
inputSchema: {
|
|
657
|
+
type: "object",
|
|
658
|
+
properties: {
|
|
659
|
+
projectId: {
|
|
660
|
+
type: "string",
|
|
661
|
+
description: "Filter completed tasks by project ID (optional)"
|
|
662
|
+
},
|
|
663
|
+
since: {
|
|
664
|
+
type: "string",
|
|
665
|
+
description: "ISO 8601 datetime to get tasks completed after this date (e.g., '2024-01-01T00:00:00Z') (optional)"
|
|
666
|
+
},
|
|
667
|
+
until: {
|
|
668
|
+
type: "string",
|
|
669
|
+
description: "ISO 8601 datetime to get tasks completed before this date (e.g., '2024-12-31T23:59:59Z') (optional)"
|
|
670
|
+
},
|
|
671
|
+
limit: {
|
|
672
|
+
type: "number",
|
|
673
|
+
description: "Maximum number of completed tasks to return (default: 30, max: 200) (optional)"
|
|
674
|
+
},
|
|
675
|
+
offset: {
|
|
676
|
+
type: "number",
|
|
677
|
+
description: "Number of completed tasks to skip for pagination (default: 0) (optional)"
|
|
678
|
+
},
|
|
679
|
+
annotateNotes: {
|
|
680
|
+
type: "boolean",
|
|
681
|
+
description: "Include note details in the response (optional)"
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
};
|
|
549
686
|
// Server implementation
|
|
550
687
|
const server = new Server({
|
|
551
688
|
name: "todoist-mcp-server-enhanced",
|
|
552
|
-
version: "0.
|
|
689
|
+
version: "0.5.0",
|
|
553
690
|
}, {
|
|
554
691
|
capabilities: {
|
|
555
692
|
tools: {},
|
|
@@ -590,6 +727,74 @@ function formatProject(project) {
|
|
|
590
727
|
function formatLabel(label) {
|
|
591
728
|
return `- ${label.name} (ID: ${label.id})${label.color ? `\n Color: ${label.color}` : ''}${label.isFavorite ? `\n Favorite: Yes` : ''}${label.order ? `\n Order: ${label.order}` : ''}`;
|
|
592
729
|
}
|
|
730
|
+
// Helper function to format comment output
|
|
731
|
+
function formatComment(comment) {
|
|
732
|
+
let commentDetails = `- ID: ${comment.id}\n Content: ${comment.content}`;
|
|
733
|
+
if (comment.postedAt)
|
|
734
|
+
commentDetails += `\n Posted At: ${comment.postedAt}`;
|
|
735
|
+
if (comment.taskId)
|
|
736
|
+
commentDetails += `\n Task ID: ${comment.taskId}`;
|
|
737
|
+
if (comment.projectId)
|
|
738
|
+
commentDetails += `\n Project ID: ${comment.projectId}`;
|
|
739
|
+
if (comment.attachment) {
|
|
740
|
+
commentDetails += `\n Attachment: ${comment.attachment.fileName || 'File'} (${comment.attachment.fileType})`;
|
|
741
|
+
if (comment.attachment.fileUrl)
|
|
742
|
+
commentDetails += `\n File URL: ${comment.attachment.fileUrl}`;
|
|
743
|
+
}
|
|
744
|
+
return commentDetails;
|
|
745
|
+
}
|
|
746
|
+
// Helper function to call Todoist Sync API
|
|
747
|
+
async function callSyncAPI(endpoint, params) {
|
|
748
|
+
const url = `https://api.todoist.com/sync/v9/${endpoint}`;
|
|
749
|
+
// Build URL-encoded body
|
|
750
|
+
const formBody = Object.entries(params)
|
|
751
|
+
.filter(([_, value]) => value !== undefined && value !== null)
|
|
752
|
+
.map(([key, value]) => {
|
|
753
|
+
const encodedKey = encodeURIComponent(key);
|
|
754
|
+
const encodedValue = encodeURIComponent(typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
755
|
+
return `${encodedKey}=${encodedValue}`;
|
|
756
|
+
})
|
|
757
|
+
.join('&');
|
|
758
|
+
const response = await fetch(url, {
|
|
759
|
+
method: 'POST',
|
|
760
|
+
headers: {
|
|
761
|
+
'Authorization': `Bearer ${TODOIST_API_TOKEN}`,
|
|
762
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
763
|
+
},
|
|
764
|
+
body: formBody,
|
|
765
|
+
});
|
|
766
|
+
if (!response.ok) {
|
|
767
|
+
const errorText = await response.text();
|
|
768
|
+
throw new Error(`Sync API error (${response.status}): ${errorText}`);
|
|
769
|
+
}
|
|
770
|
+
return response.json();
|
|
771
|
+
}
|
|
772
|
+
// Helper function to format completed task output
|
|
773
|
+
function formatCompletedTask(task, projectName, sectionName) {
|
|
774
|
+
let taskDetails = `- Task ID: ${task.task_id}`;
|
|
775
|
+
taskDetails += `\n Completion ID: ${task.id}`;
|
|
776
|
+
taskDetails += `\n Content: ${task.content}`;
|
|
777
|
+
taskDetails += `\n Completed At: ${task.completed_at}`;
|
|
778
|
+
if (projectName) {
|
|
779
|
+
taskDetails += `\n Project: ${projectName} (${task.project_id})`;
|
|
780
|
+
}
|
|
781
|
+
else if (task.project_id) {
|
|
782
|
+
taskDetails += `\n Project ID: ${task.project_id}`;
|
|
783
|
+
}
|
|
784
|
+
if (sectionName) {
|
|
785
|
+
taskDetails += `\n Section: ${sectionName} (${task.section_id})`;
|
|
786
|
+
}
|
|
787
|
+
else if (task.section_id) {
|
|
788
|
+
taskDetails += `\n Section ID: ${task.section_id}`;
|
|
789
|
+
}
|
|
790
|
+
if (task.note_count > 0) {
|
|
791
|
+
taskDetails += `\n Notes: ${task.note_count}`;
|
|
792
|
+
}
|
|
793
|
+
if (task.meta_data) {
|
|
794
|
+
taskDetails += `\n Metadata: ${JSON.stringify(task.meta_data)}`;
|
|
795
|
+
}
|
|
796
|
+
return taskDetails;
|
|
797
|
+
}
|
|
593
798
|
// Type guards for arguments
|
|
594
799
|
function isCreateTaskArgs(args) {
|
|
595
800
|
return (typeof args === "object" &&
|
|
@@ -720,6 +925,68 @@ function isUpdateLabelArgs(args) {
|
|
|
720
925
|
"labelId" in args &&
|
|
721
926
|
typeof args.labelId === "string");
|
|
722
927
|
}
|
|
928
|
+
function isCreateCommentArgs(args) {
|
|
929
|
+
if (typeof args !== "object" || args === null || !("content" in args) || typeof args.content !== "string") {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
const { taskId, projectId } = args;
|
|
933
|
+
const targets = [taskId, projectId];
|
|
934
|
+
const providedTargets = targets.filter(target => target !== undefined && target !== null && String(target).trim() !== '');
|
|
935
|
+
// Exactly one target must be provided and be a non-empty string
|
|
936
|
+
return providedTargets.length === 1 &&
|
|
937
|
+
providedTargets.every(target => typeof target === 'string');
|
|
938
|
+
}
|
|
939
|
+
function isCommentIdArgs(args) {
|
|
940
|
+
return (typeof args === "object" &&
|
|
941
|
+
args !== null &&
|
|
942
|
+
"commentId" in args &&
|
|
943
|
+
typeof args.commentId === "string");
|
|
944
|
+
}
|
|
945
|
+
function isCommentsArgs(args) {
|
|
946
|
+
if (typeof args !== "object" || args === null) {
|
|
947
|
+
return false;
|
|
948
|
+
}
|
|
949
|
+
const { taskId, projectId } = args;
|
|
950
|
+
const targets = [taskId, projectId];
|
|
951
|
+
const providedTargets = targets.filter(target => target !== undefined && target !== null && String(target).trim() !== '');
|
|
952
|
+
// Exactly one target must be provided and be a non-empty string, or no targets (for all comments)
|
|
953
|
+
return providedTargets.length <= 1 &&
|
|
954
|
+
providedTargets.every(target => typeof target === 'string');
|
|
955
|
+
}
|
|
956
|
+
function isUpdateCommentArgs(args) {
|
|
957
|
+
return (typeof args === "object" &&
|
|
958
|
+
args !== null &&
|
|
959
|
+
"commentId" in args &&
|
|
960
|
+
"content" in args &&
|
|
961
|
+
typeof args.commentId === "string" &&
|
|
962
|
+
typeof args.content === "string");
|
|
963
|
+
}
|
|
964
|
+
function isGetCompletedTasksArgs(args) {
|
|
965
|
+
if (typeof args !== "object" || args === null) {
|
|
966
|
+
return false;
|
|
967
|
+
}
|
|
968
|
+
const typed = args;
|
|
969
|
+
// All parameters are optional, so check types only if present
|
|
970
|
+
if (typed.projectId !== undefined && typeof typed.projectId !== "string") {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
if (typed.since !== undefined && typeof typed.since !== "string") {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
if (typed.until !== undefined && typeof typed.until !== "string") {
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
979
|
+
if (typed.limit !== undefined && typeof typed.limit !== "number") {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
if (typed.offset !== undefined && typeof typed.offset !== "number") {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
if (typed.annotateNotes !== undefined && typeof typed.annotateNotes !== "boolean") {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
return true;
|
|
989
|
+
}
|
|
723
990
|
// Tool handlers
|
|
724
991
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
725
992
|
tools: [
|
|
@@ -752,6 +1019,14 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
752
1019
|
GET_LABELS_TOOL,
|
|
753
1020
|
UPDATE_LABEL_TOOL,
|
|
754
1021
|
DELETE_LABEL_TOOL,
|
|
1022
|
+
// Comment tools
|
|
1023
|
+
CREATE_COMMENT_TOOL,
|
|
1024
|
+
GET_COMMENT_TOOL,
|
|
1025
|
+
GET_COMMENTS_TOOL,
|
|
1026
|
+
UPDATE_COMMENT_TOOL,
|
|
1027
|
+
DELETE_COMMENT_TOOL,
|
|
1028
|
+
// Completed tasks tool (Sync API)
|
|
1029
|
+
GET_COMPLETED_TASKS_TOOL,
|
|
755
1030
|
],
|
|
756
1031
|
}));
|
|
757
1032
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1323,6 +1598,169 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1323
1598
|
return { content: [{ type: "text", text: `Error in bulk moving tasks: ${error.message}` }], isError: true };
|
|
1324
1599
|
}
|
|
1325
1600
|
}
|
|
1601
|
+
// Comment operations
|
|
1602
|
+
if (name === "todoist_create_comment") {
|
|
1603
|
+
if (!isCreateCommentArgs(args)) {
|
|
1604
|
+
return { content: [{ type: "text", text: "Invalid arguments for create_comment" }], isError: true };
|
|
1605
|
+
}
|
|
1606
|
+
try {
|
|
1607
|
+
const commentData = { content: args.content };
|
|
1608
|
+
if (args.taskId)
|
|
1609
|
+
commentData.taskId = args.taskId;
|
|
1610
|
+
if (args.projectId)
|
|
1611
|
+
commentData.projectId = args.projectId;
|
|
1612
|
+
if (args.attachment)
|
|
1613
|
+
commentData.attachment = args.attachment;
|
|
1614
|
+
const comment = await todoistClient.addComment(commentData);
|
|
1615
|
+
return {
|
|
1616
|
+
content: [{ type: "text", text: `Comment created:\n${formatComment(comment)}` }],
|
|
1617
|
+
isError: false
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
catch (error) {
|
|
1621
|
+
return { content: [{ type: "text", text: `Error creating comment: ${error.message}` }], isError: true };
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
if (name === "todoist_get_comment") {
|
|
1625
|
+
if (!isCommentIdArgs(args)) {
|
|
1626
|
+
return { content: [{ type: "text", text: "Invalid arguments for get_comment" }], isError: true };
|
|
1627
|
+
}
|
|
1628
|
+
try {
|
|
1629
|
+
const comment = await todoistClient.getComment(args.commentId);
|
|
1630
|
+
return {
|
|
1631
|
+
content: [{ type: "text", text: `Comment details:\n${formatComment(comment)}` }],
|
|
1632
|
+
isError: false
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
catch (error) {
|
|
1636
|
+
return { content: [{ type: "text", text: `Error getting comment: ${error.message}` }], isError: true };
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
if (name === "todoist_get_comments") {
|
|
1640
|
+
if (!isCommentsArgs(args)) {
|
|
1641
|
+
return { content: [{ type: "text", text: "Invalid arguments for get_comments. Provide either taskId or projectId, not both." }], isError: true };
|
|
1642
|
+
}
|
|
1643
|
+
try {
|
|
1644
|
+
const params = {};
|
|
1645
|
+
if (args.taskId)
|
|
1646
|
+
params.taskId = args.taskId;
|
|
1647
|
+
if (args.projectId)
|
|
1648
|
+
params.projectId = args.projectId;
|
|
1649
|
+
if (args.cursor)
|
|
1650
|
+
params.cursor = args.cursor;
|
|
1651
|
+
if (args.limit)
|
|
1652
|
+
params.limit = args.limit;
|
|
1653
|
+
const commentsResponse = await todoistClient.getComments(params);
|
|
1654
|
+
const commentList = commentsResponse.results?.map(formatComment).join('\n\n') || 'No comments found';
|
|
1655
|
+
const nextCursor = commentsResponse.nextCursor ? `\n\nNext cursor for more comments: ${commentsResponse.nextCursor}` : '';
|
|
1656
|
+
return {
|
|
1657
|
+
content: [{
|
|
1658
|
+
type: "text",
|
|
1659
|
+
text: `Comments:\n${commentList}${nextCursor}`
|
|
1660
|
+
}],
|
|
1661
|
+
isError: false
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
catch (error) {
|
|
1665
|
+
return { content: [{ type: "text", text: `Error getting comments: ${error.message}` }], isError: true };
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
if (name === "todoist_update_comment") {
|
|
1669
|
+
if (!isUpdateCommentArgs(args)) {
|
|
1670
|
+
return { content: [{ type: "text", text: "Invalid arguments for update_comment" }], isError: true };
|
|
1671
|
+
}
|
|
1672
|
+
try {
|
|
1673
|
+
const { commentId, ...updateArgs } = args;
|
|
1674
|
+
const updatedComment = await todoistClient.updateComment(commentId, updateArgs);
|
|
1675
|
+
return {
|
|
1676
|
+
content: [{ type: "text", text: `Comment updated:\n${formatComment(updatedComment)}` }],
|
|
1677
|
+
isError: false
|
|
1678
|
+
};
|
|
1679
|
+
}
|
|
1680
|
+
catch (error) {
|
|
1681
|
+
return { content: [{ type: "text", text: `Error updating comment: ${error.message}` }], isError: true };
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
if (name === "todoist_delete_comment") {
|
|
1685
|
+
if (!isCommentIdArgs(args)) {
|
|
1686
|
+
return { content: [{ type: "text", text: "Invalid arguments for delete_comment" }], isError: true };
|
|
1687
|
+
}
|
|
1688
|
+
try {
|
|
1689
|
+
await todoistClient.deleteComment(args.commentId);
|
|
1690
|
+
return {
|
|
1691
|
+
content: [{ type: "text", text: `Comment ${args.commentId} deleted.` }],
|
|
1692
|
+
isError: false
|
|
1693
|
+
};
|
|
1694
|
+
}
|
|
1695
|
+
catch (error) {
|
|
1696
|
+
return { content: [{ type: "text", text: `Error deleting comment: ${error.message}` }], isError: true };
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (name === "todoist_get_completed_tasks") {
|
|
1700
|
+
if (!isGetCompletedTasksArgs(args)) {
|
|
1701
|
+
return {
|
|
1702
|
+
content: [{ type: "text", text: "Invalid arguments for get_completed_tasks" }],
|
|
1703
|
+
isError: true
|
|
1704
|
+
};
|
|
1705
|
+
}
|
|
1706
|
+
try {
|
|
1707
|
+
// Build Sync API parameters
|
|
1708
|
+
const syncParams = {};
|
|
1709
|
+
if (args.projectId)
|
|
1710
|
+
syncParams.project_id = args.projectId;
|
|
1711
|
+
if (args.since)
|
|
1712
|
+
syncParams.since = args.since;
|
|
1713
|
+
if (args.until)
|
|
1714
|
+
syncParams.until = args.until;
|
|
1715
|
+
if (args.limit !== undefined)
|
|
1716
|
+
syncParams.limit = Math.min(args.limit, 200);
|
|
1717
|
+
if (args.offset !== undefined)
|
|
1718
|
+
syncParams.offset = args.offset;
|
|
1719
|
+
if (args.annotateNotes)
|
|
1720
|
+
syncParams.annotate_notes = true;
|
|
1721
|
+
// Call Sync API
|
|
1722
|
+
const response = await callSyncAPI('completed/get_all', syncParams);
|
|
1723
|
+
if (!response.items || response.items.length === 0) {
|
|
1724
|
+
return {
|
|
1725
|
+
content: [{
|
|
1726
|
+
type: "text",
|
|
1727
|
+
text: "No completed tasks found matching the specified criteria"
|
|
1728
|
+
}],
|
|
1729
|
+
isError: false
|
|
1730
|
+
};
|
|
1731
|
+
}
|
|
1732
|
+
// Format completed tasks with project/section names if available
|
|
1733
|
+
const formattedTasks = response.items.map(task => {
|
|
1734
|
+
const projectName = response.projects?.[task.project_id]?.name;
|
|
1735
|
+
const sectionName = response.sections?.[task.section_id || '']?.name;
|
|
1736
|
+
return formatCompletedTask(task, projectName, sectionName);
|
|
1737
|
+
});
|
|
1738
|
+
const taskList = formattedTasks.join('\n\n');
|
|
1739
|
+
// Build pagination info
|
|
1740
|
+
let paginationInfo = '';
|
|
1741
|
+
if (response.has_more || (args.limit && response.items.length === args.limit)) {
|
|
1742
|
+
const nextOffset = (args.offset || 0) + response.items.length;
|
|
1743
|
+
paginationInfo = `\n\nPagination: Showing ${response.items.length} task(s). `;
|
|
1744
|
+
paginationInfo += `Use offset=${nextOffset} to retrieve the next page.`;
|
|
1745
|
+
}
|
|
1746
|
+
return {
|
|
1747
|
+
content: [{
|
|
1748
|
+
type: "text",
|
|
1749
|
+
text: `Completed Tasks (${response.items.length} found):\n\n${taskList}${paginationInfo}`
|
|
1750
|
+
}],
|
|
1751
|
+
isError: false
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
catch (error) {
|
|
1755
|
+
return {
|
|
1756
|
+
content: [{
|
|
1757
|
+
type: "text",
|
|
1758
|
+
text: `Error getting completed tasks: ${error.message}`
|
|
1759
|
+
}],
|
|
1760
|
+
isError: true
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1326
1764
|
return {
|
|
1327
1765
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1328
1766
|
isError: true,
|
package/package.json
CHANGED