@kydycode/todoist-mcp-server-ext 0.3.0 → 0.4.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 +21 -2
- package/dist/index.js +260 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,13 @@ A comprehensive MCP (Model Context Protocol) server implementation that provides
|
|
|
40
40
|
* **Label Customization**: Set names, colors, favorites, order.
|
|
41
41
|
* **Paginated Label Listing**: Efficiently retrieve all labels.
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
### 💬 **Comment Management (5 Tools)**
|
|
44
|
+
* **Complete Comment CRUD**: Create, read, update, delete comments on tasks and projects.
|
|
45
|
+
* **Attachment Support**: Add file attachments to comments with metadata.
|
|
46
|
+
* **Flexible Targeting**: Comments can be attached to either tasks or projects.
|
|
47
|
+
* **Paginated Comment Retrieval**: Efficiently browse through comment threads.
|
|
48
|
+
|
|
49
|
+
## 🛠️ Available Tools (Total 29)
|
|
44
50
|
|
|
45
51
|
### Task Operations (10 tools)
|
|
46
52
|
| Tool | Description |
|
|
@@ -82,6 +88,15 @@ A comprehensive MCP (Model Context Protocol) server implementation that provides
|
|
|
82
88
|
| `todoist_update_label` | Update an existing label by its ID (name, color, favorite, order). |
|
|
83
89
|
| `todoist_delete_label` | Delete a label by its ID. |
|
|
84
90
|
|
|
91
|
+
### Comment Operations (5 tools)
|
|
92
|
+
| Tool | Description |
|
|
93
|
+
|----------------------------|-----------------------------------------------------------------|
|
|
94
|
+
| `todoist_create_comment` | Create a new comment on a task or project (with attachments). |
|
|
95
|
+
| `todoist_get_comment` | Get a specific comment by its ID. |
|
|
96
|
+
| `todoist_get_comments` | Get comments for a task or project with pagination support. |
|
|
97
|
+
| `todoist_update_comment` | Update an existing comment by its ID. |
|
|
98
|
+
| `todoist_delete_comment` | Delete a comment by its ID. |
|
|
99
|
+
|
|
85
100
|
## 🚀 Installation & Setup
|
|
86
101
|
|
|
87
102
|
### Local Development Setup
|
|
@@ -166,13 +181,16 @@ npm install -g @kydycode/todoist-mcp-server-ext@latest
|
|
|
166
181
|
"Search tasks: search: API deployment"
|
|
167
182
|
```
|
|
168
183
|
|
|
169
|
-
### 🗂️ Project, Section, and
|
|
184
|
+
### 🗂️ Project, Section, Label, and Comment Management
|
|
170
185
|
```
|
|
171
186
|
"List all my projects"
|
|
172
187
|
"Create project 'Q2 Planning' color:blue favorite:true view:board"
|
|
173
188
|
"Get sections for project {project_id}"
|
|
174
189
|
"Create label 'HighPriority' color:red isFavorite:true"
|
|
175
190
|
"List all labels"
|
|
191
|
+
"Add comment 'Great progress on this task!' to task {task_id}"
|
|
192
|
+
"Get all comments for project {project_id}"
|
|
193
|
+
"Update comment {comment_id} with new content"
|
|
176
194
|
```
|
|
177
195
|
|
|
178
196
|
## 🆚 Extended vs Original Comparison
|
|
@@ -187,6 +205,7 @@ npm install -g @kydycode/todoist-mcp-server-ext@latest
|
|
|
187
205
|
| **Project Management** | Limited | ✅ 5 Tools: Full CRUD operations, sub-projects, pagination |
|
|
188
206
|
| **Section Management** | Basic | ✅ 4 Tools: Complete section operations |
|
|
189
207
|
| **Label Management** | Not Available | ✅ 5 Tools: Full CRUD operations, pagination |
|
|
208
|
+
| **Comment Management** | Not Available | ✅ 5 Tools: Full CRUD operations, attachments, pagination |
|
|
190
209
|
| **API Parameter Handling** | Inconsistent | ✅ Proper parameter validation |
|
|
191
210
|
| **Response Formatting** | Basic | ✅ Enhanced readability, more details |
|
|
192
211
|
| **Build System** | Issues | ✅ Clean compilation |
|
package/dist/index.js
CHANGED
|
@@ -546,6 +546,110 @@ 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
|
+
};
|
|
549
653
|
// Server implementation
|
|
550
654
|
const server = new Server({
|
|
551
655
|
name: "todoist-mcp-server-enhanced",
|
|
@@ -590,6 +694,22 @@ function formatProject(project) {
|
|
|
590
694
|
function formatLabel(label) {
|
|
591
695
|
return `- ${label.name} (ID: ${label.id})${label.color ? `\n Color: ${label.color}` : ''}${label.isFavorite ? `\n Favorite: Yes` : ''}${label.order ? `\n Order: ${label.order}` : ''}`;
|
|
592
696
|
}
|
|
697
|
+
// Helper function to format comment output
|
|
698
|
+
function formatComment(comment) {
|
|
699
|
+
let commentDetails = `- ID: ${comment.id}\n Content: ${comment.content}`;
|
|
700
|
+
if (comment.postedAt)
|
|
701
|
+
commentDetails += `\n Posted At: ${comment.postedAt}`;
|
|
702
|
+
if (comment.taskId)
|
|
703
|
+
commentDetails += `\n Task ID: ${comment.taskId}`;
|
|
704
|
+
if (comment.projectId)
|
|
705
|
+
commentDetails += `\n Project ID: ${comment.projectId}`;
|
|
706
|
+
if (comment.attachment) {
|
|
707
|
+
commentDetails += `\n Attachment: ${comment.attachment.fileName || 'File'} (${comment.attachment.fileType})`;
|
|
708
|
+
if (comment.attachment.fileUrl)
|
|
709
|
+
commentDetails += `\n File URL: ${comment.attachment.fileUrl}`;
|
|
710
|
+
}
|
|
711
|
+
return commentDetails;
|
|
712
|
+
}
|
|
593
713
|
// Type guards for arguments
|
|
594
714
|
function isCreateTaskArgs(args) {
|
|
595
715
|
return (typeof args === "object" &&
|
|
@@ -720,6 +840,42 @@ function isUpdateLabelArgs(args) {
|
|
|
720
840
|
"labelId" in args &&
|
|
721
841
|
typeof args.labelId === "string");
|
|
722
842
|
}
|
|
843
|
+
function isCreateCommentArgs(args) {
|
|
844
|
+
if (typeof args !== "object" || args === null || !("content" in args) || typeof args.content !== "string") {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
const { taskId, projectId } = args;
|
|
848
|
+
const targets = [taskId, projectId];
|
|
849
|
+
const providedTargets = targets.filter(target => target !== undefined && target !== null && String(target).trim() !== '');
|
|
850
|
+
// Exactly one target must be provided and be a non-empty string
|
|
851
|
+
return providedTargets.length === 1 &&
|
|
852
|
+
providedTargets.every(target => typeof target === 'string');
|
|
853
|
+
}
|
|
854
|
+
function isCommentIdArgs(args) {
|
|
855
|
+
return (typeof args === "object" &&
|
|
856
|
+
args !== null &&
|
|
857
|
+
"commentId" in args &&
|
|
858
|
+
typeof args.commentId === "string");
|
|
859
|
+
}
|
|
860
|
+
function isCommentsArgs(args) {
|
|
861
|
+
if (typeof args !== "object" || args === null) {
|
|
862
|
+
return false;
|
|
863
|
+
}
|
|
864
|
+
const { taskId, projectId } = args;
|
|
865
|
+
const targets = [taskId, projectId];
|
|
866
|
+
const providedTargets = targets.filter(target => target !== undefined && target !== null && String(target).trim() !== '');
|
|
867
|
+
// Exactly one target must be provided and be a non-empty string, or no targets (for all comments)
|
|
868
|
+
return providedTargets.length <= 1 &&
|
|
869
|
+
providedTargets.every(target => typeof target === 'string');
|
|
870
|
+
}
|
|
871
|
+
function isUpdateCommentArgs(args) {
|
|
872
|
+
return (typeof args === "object" &&
|
|
873
|
+
args !== null &&
|
|
874
|
+
"commentId" in args &&
|
|
875
|
+
"content" in args &&
|
|
876
|
+
typeof args.commentId === "string" &&
|
|
877
|
+
typeof args.content === "string");
|
|
878
|
+
}
|
|
723
879
|
// Tool handlers
|
|
724
880
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
725
881
|
tools: [
|
|
@@ -752,6 +908,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
752
908
|
GET_LABELS_TOOL,
|
|
753
909
|
UPDATE_LABEL_TOOL,
|
|
754
910
|
DELETE_LABEL_TOOL,
|
|
911
|
+
// Comment tools
|
|
912
|
+
CREATE_COMMENT_TOOL,
|
|
913
|
+
GET_COMMENT_TOOL,
|
|
914
|
+
GET_COMMENTS_TOOL,
|
|
915
|
+
UPDATE_COMMENT_TOOL,
|
|
916
|
+
DELETE_COMMENT_TOOL,
|
|
755
917
|
],
|
|
756
918
|
}));
|
|
757
919
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1323,6 +1485,104 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1323
1485
|
return { content: [{ type: "text", text: `Error in bulk moving tasks: ${error.message}` }], isError: true };
|
|
1324
1486
|
}
|
|
1325
1487
|
}
|
|
1488
|
+
// Comment operations
|
|
1489
|
+
if (name === "todoist_create_comment") {
|
|
1490
|
+
if (!isCreateCommentArgs(args)) {
|
|
1491
|
+
return { content: [{ type: "text", text: "Invalid arguments for create_comment" }], isError: true };
|
|
1492
|
+
}
|
|
1493
|
+
try {
|
|
1494
|
+
const commentData = { content: args.content };
|
|
1495
|
+
if (args.taskId)
|
|
1496
|
+
commentData.taskId = args.taskId;
|
|
1497
|
+
if (args.projectId)
|
|
1498
|
+
commentData.projectId = args.projectId;
|
|
1499
|
+
if (args.attachment)
|
|
1500
|
+
commentData.attachment = args.attachment;
|
|
1501
|
+
const comment = await todoistClient.addComment(commentData);
|
|
1502
|
+
return {
|
|
1503
|
+
content: [{ type: "text", text: `Comment created:\n${formatComment(comment)}` }],
|
|
1504
|
+
isError: false
|
|
1505
|
+
};
|
|
1506
|
+
}
|
|
1507
|
+
catch (error) {
|
|
1508
|
+
return { content: [{ type: "text", text: `Error creating comment: ${error.message}` }], isError: true };
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
if (name === "todoist_get_comment") {
|
|
1512
|
+
if (!isCommentIdArgs(args)) {
|
|
1513
|
+
return { content: [{ type: "text", text: "Invalid arguments for get_comment" }], isError: true };
|
|
1514
|
+
}
|
|
1515
|
+
try {
|
|
1516
|
+
const comment = await todoistClient.getComment(args.commentId);
|
|
1517
|
+
return {
|
|
1518
|
+
content: [{ type: "text", text: `Comment details:\n${formatComment(comment)}` }],
|
|
1519
|
+
isError: false
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
catch (error) {
|
|
1523
|
+
return { content: [{ type: "text", text: `Error getting comment: ${error.message}` }], isError: true };
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (name === "todoist_get_comments") {
|
|
1527
|
+
if (!isCommentsArgs(args)) {
|
|
1528
|
+
return { content: [{ type: "text", text: "Invalid arguments for get_comments. Provide either taskId or projectId, not both." }], isError: true };
|
|
1529
|
+
}
|
|
1530
|
+
try {
|
|
1531
|
+
const params = {};
|
|
1532
|
+
if (args.taskId)
|
|
1533
|
+
params.taskId = args.taskId;
|
|
1534
|
+
if (args.projectId)
|
|
1535
|
+
params.projectId = args.projectId;
|
|
1536
|
+
if (args.cursor)
|
|
1537
|
+
params.cursor = args.cursor;
|
|
1538
|
+
if (args.limit)
|
|
1539
|
+
params.limit = args.limit;
|
|
1540
|
+
const commentsResponse = await todoistClient.getComments(params);
|
|
1541
|
+
const commentList = commentsResponse.results?.map(formatComment).join('\n\n') || 'No comments found';
|
|
1542
|
+
const nextCursor = commentsResponse.nextCursor ? `\n\nNext cursor for more comments: ${commentsResponse.nextCursor}` : '';
|
|
1543
|
+
return {
|
|
1544
|
+
content: [{
|
|
1545
|
+
type: "text",
|
|
1546
|
+
text: `Comments:\n${commentList}${nextCursor}`
|
|
1547
|
+
}],
|
|
1548
|
+
isError: false
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
catch (error) {
|
|
1552
|
+
return { content: [{ type: "text", text: `Error getting comments: ${error.message}` }], isError: true };
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
if (name === "todoist_update_comment") {
|
|
1556
|
+
if (!isUpdateCommentArgs(args)) {
|
|
1557
|
+
return { content: [{ type: "text", text: "Invalid arguments for update_comment" }], isError: true };
|
|
1558
|
+
}
|
|
1559
|
+
try {
|
|
1560
|
+
const { commentId, ...updateArgs } = args;
|
|
1561
|
+
const updatedComment = await todoistClient.updateComment(commentId, updateArgs);
|
|
1562
|
+
return {
|
|
1563
|
+
content: [{ type: "text", text: `Comment updated:\n${formatComment(updatedComment)}` }],
|
|
1564
|
+
isError: false
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
catch (error) {
|
|
1568
|
+
return { content: [{ type: "text", text: `Error updating comment: ${error.message}` }], isError: true };
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
if (name === "todoist_delete_comment") {
|
|
1572
|
+
if (!isCommentIdArgs(args)) {
|
|
1573
|
+
return { content: [{ type: "text", text: "Invalid arguments for delete_comment" }], isError: true };
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
await todoistClient.deleteComment(args.commentId);
|
|
1577
|
+
return {
|
|
1578
|
+
content: [{ type: "text", text: `Comment ${args.commentId} deleted.` }],
|
|
1579
|
+
isError: false
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
catch (error) {
|
|
1583
|
+
return { content: [{ type: "text", text: `Error deleting comment: ${error.message}` }], isError: true };
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1326
1586
|
return {
|
|
1327
1587
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
1328
1588
|
isError: true,
|
package/package.json
CHANGED