Skip to content
This repository was archived by the owner on Sep 16, 2025. It is now read-only.

Commit 2fdff57

Browse files
authored
feat: add due date and duration field support (#29)
**Description:** This PR enhances task functionality by adding comprehensive support for due date and duration fields to the Todoist MCP tools. I'm referring to issue #18. I'm aware of #19, but this PR supports deadlines, due dates, and durations. In particular, the lack of due date support is really unfortunate, so I tried to address it. If something isn't exactly as you need it, please feel free to take whatever is helpful and close the PR. **Features:** - Added support for natural language due dates (e.g., "tomorrow at 3pm"). - Added support for specific dates in YYYY-MM-DD format. - Added full ISO datetime format support. - Added language specification for due dates. - Added task duration tracking with customizable units. **Implementation:** - Enhanced the `add-task.ts` and `update-task.ts` tools with new optional parameters. - Added proper validation using Zod schema. - Maintained backward compatibility with existing implementations. **Benefits:** These enhancements allow for more precise task scheduling and time management capabilities within the Todoist integration.
1 parent b27ff60 commit 2fdff57

File tree

2 files changed

+132
-8
lines changed

2 files changed

+132
-8
lines changed

src/tools/add-task.ts

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { TodoistApi } from '@doist/todoist-api-typescript'
1+
import type { AddTaskArgs, TodoistApi } from '@doist/todoist-api-typescript'
22
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
33
import { z } from 'zod'
44

@@ -22,35 +22,98 @@ export function registerAddTask(server: McpServer, api: TodoistApi) {
2222
.describe('Task priority from 1 (normal) to 4 (urgent)'),
2323
labels: z.array(z.string()).optional(),
2424
parentId: z.string().optional().describe('The ID of a parent task'),
25+
dueString: z
26+
.string()
27+
.optional()
28+
.describe('Natural language description of due date like "tomorrow at 3pm"'),
29+
dueLang: z
30+
.string()
31+
.optional()
32+
.describe('2-letter code specifying language of due date'),
33+
dueDate: z
34+
.string()
35+
.optional()
36+
.describe("Specific date in YYYY-MM-DD format relative to user's timezone"),
37+
dueDatetime: z
38+
.string()
39+
.optional()
40+
.describe('Full ISO datetime format like "2023-12-31T15:00:00Z"'),
2541
deadlineDate: z
2642
.string()
2743
.optional()
28-
.describe('Specific date in YYYY-MM-DD format relative to users timezone.'),
44+
.describe("Specific date in YYYY-MM-DD format relative to user's timezone."),
2945
deadlineLang: z
3046
.string()
3147
.optional()
3248
.describe('2-letter code specifying language of deadline.'),
49+
duration: z
50+
.number()
51+
.optional()
52+
.describe('Duration of the task (must be provided with durationUnit)'),
53+
durationUnit: z
54+
.enum(['minute', 'day'])
55+
.optional()
56+
.describe('Unit for task duration (must be provided with duration)'),
3357
},
3458
async ({
3559
content,
60+
description,
3661
projectId,
3762
parentId,
3863
assigneeId,
3964
priority,
4065
labels,
66+
dueString,
67+
dueLang,
68+
dueDate,
69+
dueDatetime,
4170
deadlineDate,
4271
deadlineLang,
72+
duration,
73+
durationUnit,
4374
}) => {
44-
const task = await api.addTask({
75+
// Validate that dueDate and dueDatetime are not both provided
76+
if (dueDate && dueDatetime) {
77+
throw new Error('Cannot provide both dueDate and dueDatetime')
78+
}
79+
80+
// Validate that if duration or durationUnit is provided, both must be provided
81+
if ((duration && !durationUnit) || (!duration && durationUnit)) {
82+
throw new Error('Must provide both duration and durationUnit, or neither')
83+
}
84+
85+
// Create base task args
86+
const baseArgs = {
4587
content,
88+
description,
4689
projectId,
4790
parentId,
4891
assigneeId,
4992
priority,
5093
labels,
94+
dueString,
95+
dueLang,
5196
deadlineDate,
5297
deadlineLang,
53-
})
98+
}
99+
100+
// Handle due date (can only have one of dueDate or dueDatetime)
101+
let taskArgs: Partial<AddTaskArgs> = {}
102+
if (dueDate) {
103+
taskArgs = { ...baseArgs, dueDate }
104+
} else if (dueDatetime) {
105+
taskArgs = { ...baseArgs, dueDatetime }
106+
} else {
107+
taskArgs = baseArgs
108+
}
109+
110+
// Handle duration (must have both or neither)
111+
if (duration !== undefined && durationUnit !== undefined) {
112+
taskArgs = { ...taskArgs, duration, durationUnit }
113+
}
114+
115+
const task = await api.addTask(taskArgs as AddTaskArgs)
116+
54117
return {
55118
content: [
56119
{

src/tools/update-task.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { TodoistApi } from '@doist/todoist-api-typescript'
1+
import type { TodoistApi, UpdateTaskArgs } from '@doist/todoist-api-typescript'
22
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
33
import { z } from 'zod'
44

@@ -21,14 +21,38 @@ export function registerUpdateTask(server: McpServer, api: TodoistApi) {
2121
.optional()
2222
.describe('Task priority from 1 (normal) to 4 (urgent)'),
2323
labels: z.array(z.string()).optional(),
24+
dueString: z
25+
.string()
26+
.optional()
27+
.describe('Natural language description of due date like "tomorrow at 3pm"'),
28+
dueLang: z
29+
.string()
30+
.optional()
31+
.describe('2-letter code specifying language of due date'),
32+
dueDate: z
33+
.string()
34+
.optional()
35+
.describe("Specific date in YYYY-MM-DD format relative to user's timezone"),
36+
dueDatetime: z
37+
.string()
38+
.optional()
39+
.describe('Full ISO datetime format like "2023-12-31T15:00:00Z"'),
2440
deadlineDate: z
2541
.string()
2642
.optional()
27-
.describe('Specific date in YYYY-MM-DD format relative to users timezone.'),
43+
.describe("Specific date in YYYY-MM-DD format relative to user's timezone."),
2844
deadlineLang: z
2945
.string()
3046
.optional()
3147
.describe('2-letter code specifying language of deadline.'),
48+
duration: z
49+
.number()
50+
.optional()
51+
.describe('Duration of the task (must be provided with durationUnit)'),
52+
durationUnit: z
53+
.enum(['minute', 'day'])
54+
.optional()
55+
.describe('Unit for task duration (must be provided with duration)'),
3256
},
3357
async ({
3458
taskId,
@@ -37,18 +61,55 @@ export function registerUpdateTask(server: McpServer, api: TodoistApi) {
3761
assigneeId,
3862
priority,
3963
labels,
64+
dueString,
65+
dueLang,
66+
dueDate,
67+
dueDatetime,
4068
deadlineDate,
4169
deadlineLang,
70+
duration,
71+
durationUnit,
4272
}) => {
43-
const task = await api.updateTask(taskId, {
73+
// Validate that dueDate and dueDatetime are not both provided
74+
if (dueDate && dueDatetime) {
75+
throw new Error('Cannot provide both dueDate and dueDatetime')
76+
}
77+
78+
// Validate that if duration or durationUnit is provided, both must be provided
79+
if ((duration && !durationUnit) || (!duration && durationUnit)) {
80+
throw new Error('Must provide both duration and durationUnit, or neither')
81+
}
82+
83+
// Create base update args
84+
const baseArgs = {
4485
content,
4586
description,
4687
assigneeId,
4788
priority,
4889
labels,
90+
dueString,
91+
dueLang,
4992
deadlineDate,
5093
deadlineLang,
51-
})
94+
}
95+
96+
// Handle due date (can only have one of dueDate or dueDatetime)
97+
let updateArgs: Partial<UpdateTaskArgs> = {}
98+
if (dueDate) {
99+
updateArgs = { ...baseArgs, dueDate }
100+
} else if (dueDatetime) {
101+
updateArgs = { ...baseArgs, dueDatetime }
102+
} else {
103+
updateArgs = baseArgs
104+
}
105+
106+
// Handle duration (must have both or neither)
107+
if (duration !== undefined && durationUnit !== undefined) {
108+
updateArgs = { ...updateArgs, duration, durationUnit }
109+
}
110+
111+
const task = await api.updateTask(taskId, updateArgs as UpdateTaskArgs)
112+
52113
return {
53114
content: [{ type: 'text', text: JSON.stringify(task, null, 2) }],
54115
}

0 commit comments

Comments
 (0)