@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.
Files changed (3) hide show
  1. package/README.md +47 -5
  2. package/dist/index.js +439 -1
  3. 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 (10 Tools)**
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
- ## 🛠️ Available Tools (Total 24)
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
- ### Task Operations (10 tools)
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 Label Management
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 | ✅ 10 Tools: Direct ID-based, `moveTasks`, robust search, QuickAdd, full CRUD-like ops |
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.2.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kydycode/todoist-mcp-server-ext",
3
- "version": "0.3.0",
3
+ "version": "0.5.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",