todoist-mcp 1.0.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 +130 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -0
- package/dist/tools/comments.d.ts +4 -0
- package/dist/tools/comments.js +141 -0
- package/dist/tools/create_task.d.ts +4 -0
- package/dist/tools/create_task.js +179 -0
- package/dist/tools/example.d.ts +14 -0
- package/dist/tools/example.js +99 -0
- package/dist/tools/example.test.d.ts +1 -0
- package/dist/tools/example.test.js +123 -0
- package/dist/tools/get_tasks.d.ts +4 -0
- package/dist/tools/get_tasks.js +92 -0
- package/dist/tools/labels.d.ts +4 -0
- package/dist/tools/labels.js +207 -0
- package/dist/tools/projects.d.ts +4 -0
- package/dist/tools/projects.js +171 -0
- package/dist/tools/sections.d.ts +4 -0
- package/dist/tools/sections.js +131 -0
- package/dist/tools/task_tools.d.ts +4 -0
- package/dist/tools/task_tools.js +284 -0
- package/dist/tools/tasks.d.ts +4 -0
- package/dist/tools/tasks.js +283 -0
- package/dist/tools/tests/example_data.d.ts +21 -0
- package/dist/tools/tests/example_data.js +65 -0
- package/dist/tools/tests/get_tasks.test.d.ts +1 -0
- package/dist/tools/tests/get_tasks.test.js +60 -0
- package/dist/tools/utils.d.ts +4 -0
- package/dist/tools/utils.js +31 -0
- package/dist/tools.d.ts +15 -0
- package/dist/tools.js +22 -0
- package/dist/utils/TestClient.d.ts +17 -0
- package/dist/utils/TestClient.js +23 -0
- package/dist/utils/TodoistClient.d.ts +26 -0
- package/dist/utils/TodoistClient.js +87 -0
- package/dist/utils/handlers.d.ts +14 -0
- package/dist/utils/handlers.js +69 -0
- package/dist/utils/helpers.d.ts +7 -0
- package/dist/utils/helpers.js +13 -0
- package/dist/utils/types.d.ts +10 -0
- package/dist/utils/types.js +1 -0
- package/dist/utils/version.d.ts +1 -0
- package/dist/utils/version.js +7 -0
- package/package.json +60 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<h1></h1>
|
|
3
|
+
<img src="https://static-00.iconduck.com/assets.00/todoist-icon-512x512-v3a6dxo9.png" width="120"/>
|
|
4
|
+
<h1>Todoist MCP Server</h1>
|
|
5
|
+
<p>A Model Context Protocol (MCP) server implementation that integrates Claude and other AI assistants with Todoist, enabling natural language task management.</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
* **Complete Todoist API Integration**: Access to the full Todoist REST API v2 through natural language
|
|
11
|
+
* **Tasks Management**: Create, update, close, reopen, and delete tasks using conversational language
|
|
12
|
+
* **Project Management**: Create and manage projects and sections
|
|
13
|
+
* **Comments Support**: Add and manage comments on tasks and projects
|
|
14
|
+
* **Label Management**: Create and manage personal and shared labels
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
You'll need a Todoist API token to use this MCP server.
|
|
19
|
+
|
|
20
|
+
### Getting a Todoist API Token
|
|
21
|
+
|
|
22
|
+
1. Log in to your Todoist account
|
|
23
|
+
2. Navigate to Settings → Integrations
|
|
24
|
+
3. Find your API token under "Developer"
|
|
25
|
+
|
|
26
|
+
### Usage with Claude Desktop
|
|
27
|
+
|
|
28
|
+
Add to your `claude_desktop_config.json`:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"todoist": {
|
|
34
|
+
"command": "npx",
|
|
35
|
+
"args": [
|
|
36
|
+
"-y",
|
|
37
|
+
"todoist-mcp"
|
|
38
|
+
],
|
|
39
|
+
"env": {
|
|
40
|
+
"API_KEY": "your_todoist_api_token_here"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Available Tools
|
|
48
|
+
|
|
49
|
+
### Tasks
|
|
50
|
+
|
|
51
|
+
- `get_tasks`: Retrieve tasks with optional filtering
|
|
52
|
+
- `create_task`: Create a new task with various attributes
|
|
53
|
+
- `get_task`: Get a specific task by ID
|
|
54
|
+
- `update_task`: Update an existing task
|
|
55
|
+
- `close_task`: Mark a task as complete
|
|
56
|
+
- `reopen_task`: Reopen a completed task
|
|
57
|
+
- `delete_task`: Delete a task
|
|
58
|
+
|
|
59
|
+
### Projects
|
|
60
|
+
|
|
61
|
+
- `get_projects`: Get all projects
|
|
62
|
+
- `create_project`: Create a new project
|
|
63
|
+
- `get_project`: Get a specific project by ID
|
|
64
|
+
- `update_project`: Update an existing project
|
|
65
|
+
- `delete_project`: Delete a project
|
|
66
|
+
- `get_collaborators`: Get all collaborators for a project
|
|
67
|
+
|
|
68
|
+
### Sections
|
|
69
|
+
|
|
70
|
+
- `get_sections`: Get all sections or filter by project
|
|
71
|
+
- `create_section`: Create a new section
|
|
72
|
+
- `get_section`: Get a specific section by ID
|
|
73
|
+
- `update_section`: Update a section
|
|
74
|
+
- `delete_section`: Delete a section
|
|
75
|
+
|
|
76
|
+
### Comments
|
|
77
|
+
|
|
78
|
+
- `get_comments`: Get comments for a project or task
|
|
79
|
+
- `create_comment`: Create a new comment
|
|
80
|
+
- `get_comment`: Get a specific comment by ID
|
|
81
|
+
- `update_comment`: Update a comment
|
|
82
|
+
- `delete_comment`: Delete a comment
|
|
83
|
+
|
|
84
|
+
### Labels
|
|
85
|
+
|
|
86
|
+
- `get_labels`: Get all personal labels
|
|
87
|
+
- `create_label`: Create a new personal label
|
|
88
|
+
- `get_label`: Get a personal label by ID
|
|
89
|
+
- `update_label`: Update a personal label
|
|
90
|
+
- `delete_label`: Delete a personal label
|
|
91
|
+
- `get_shared_labels`: Get all shared labels
|
|
92
|
+
- `rename_shared_label`: Rename a shared label
|
|
93
|
+
- `remove_shared_label`: Remove a shared label
|
|
94
|
+
|
|
95
|
+
### Utilities
|
|
96
|
+
|
|
97
|
+
- `utils_get_colors`: Get available colors for projects, labels, and filters
|
|
98
|
+
|
|
99
|
+
## Example Usage
|
|
100
|
+
|
|
101
|
+
Ask your AI assistant (like Claude) questions such as:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
"What tasks do I have due today?"
|
|
105
|
+
"Create a task to review the quarterly report by next Friday"
|
|
106
|
+
"Make a new project called 'Home Renovation'"
|
|
107
|
+
"Add a comment to my meeting prep task"
|
|
108
|
+
"Show me all my high priority tasks"
|
|
109
|
+
"Create a label for 'Urgent' tasks with a red color"
|
|
110
|
+
"What projects do I have in my Todoist?"
|
|
111
|
+
"Mark my dentist appointment task as complete"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Development
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# Install dependencies
|
|
118
|
+
npm install
|
|
119
|
+
|
|
120
|
+
# Build the project and run inspector
|
|
121
|
+
npm run build && npx @modelcontextprotocol/inspector -e API_KEY=YOUR_API_KEY_HERE node dist/index.js
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
127
|
+
|
|
128
|
+
## Issues and Support
|
|
129
|
+
|
|
130
|
+
If you encounter any issues or need support, please file an issue on the GitHub repository.
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { config, log } from './utils/helpers.js';
|
|
6
|
+
import { version } from './utils/version.js';
|
|
7
|
+
import { ALL_HANDLERS, ALL_TOOLS } from "./tools.js";
|
|
8
|
+
const server = new Server({
|
|
9
|
+
name: 'todoist-mcp',
|
|
10
|
+
version
|
|
11
|
+
}, { capabilities: { tools: {} } });
|
|
12
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
13
|
+
return { tools: ALL_TOOLS };
|
|
14
|
+
});
|
|
15
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
16
|
+
const toolName = request.params.name;
|
|
17
|
+
log('Tool call: ', toolName);
|
|
18
|
+
try {
|
|
19
|
+
const handler = ALL_HANDLERS[toolName];
|
|
20
|
+
if (!handler) {
|
|
21
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
22
|
+
}
|
|
23
|
+
return await handler(request);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
log('Error handling tool call:', error);
|
|
27
|
+
return {
|
|
28
|
+
content: [
|
|
29
|
+
{
|
|
30
|
+
type: 'text',
|
|
31
|
+
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
isError: true,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
export async function main() {
|
|
39
|
+
if (!config.API_KEY || config.API_KEY.length === 0) {
|
|
40
|
+
log('Missing required configuration: TODOIST_API_TOKEN');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const transport = new StdioServerTransport();
|
|
45
|
+
await server.connect(transport);
|
|
46
|
+
log('Server connected and running');
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
log('Fatal error:', error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
main().then();
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import z from 'zod';
|
|
2
|
+
import { createApiHandler } from "../utils/handlers.js";
|
|
3
|
+
export const COMMENTS_TOOLS = [
|
|
4
|
+
{
|
|
5
|
+
name: 'get_comments',
|
|
6
|
+
description: 'Get all comments from Todoist, must be provided or `project_id` or `task_id`',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
required: [],
|
|
10
|
+
properties: {
|
|
11
|
+
project_id: {
|
|
12
|
+
type: "string",
|
|
13
|
+
description: 'ID of the project used to filter comments'
|
|
14
|
+
},
|
|
15
|
+
task_id: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: 'ID of the task used to filter comments'
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'create_comment',
|
|
24
|
+
description: 'Create a new comment in Todoist, must be provided or `project_id` or `task_id`',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
required: ["content"],
|
|
28
|
+
properties: {
|
|
29
|
+
task_id: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: 'Comment\'s task ID (for task comments)'
|
|
32
|
+
},
|
|
33
|
+
project_id: {
|
|
34
|
+
type: "string",
|
|
35
|
+
description: 'Comment\'s project ID (for project comments)'
|
|
36
|
+
},
|
|
37
|
+
content: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: 'Comment markdown-formatted text and hyperlinks'
|
|
40
|
+
},
|
|
41
|
+
// attachment: {
|
|
42
|
+
// type: "object",
|
|
43
|
+
// description: 'Object for attachment object'
|
|
44
|
+
// }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'get_comment',
|
|
50
|
+
description: 'Get a comment from Todoist by ID',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: "object",
|
|
53
|
+
required: ["id"],
|
|
54
|
+
properties: {
|
|
55
|
+
id: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: 'ID of the comment to retrieve'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'update_comment',
|
|
64
|
+
description: 'Update a comment in Todoist',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
required: ["id", "content"],
|
|
68
|
+
properties: {
|
|
69
|
+
id: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: 'ID of the comment to update'
|
|
72
|
+
},
|
|
73
|
+
content: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: 'New content, markdown-formatted text and hyperlinks'
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'delete_comment',
|
|
82
|
+
description: 'Delete a comment in Todoist',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: "object",
|
|
85
|
+
required: ["id"],
|
|
86
|
+
properties: {
|
|
87
|
+
id: {
|
|
88
|
+
type: "string",
|
|
89
|
+
description: 'ID of the comment to delete'
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
];
|
|
95
|
+
export const COMMENT_HANDLERS = {
|
|
96
|
+
get_comments: createApiHandler({
|
|
97
|
+
schemaShape: {
|
|
98
|
+
project_id: z.string().optional(),
|
|
99
|
+
task_id: z.string().optional(),
|
|
100
|
+
},
|
|
101
|
+
method: 'GET',
|
|
102
|
+
path: '/comments',
|
|
103
|
+
errorPrefix: 'Failed to get comments',
|
|
104
|
+
}),
|
|
105
|
+
create_comment: createApiHandler({
|
|
106
|
+
schemaShape: {
|
|
107
|
+
task_id: z.string().optional(),
|
|
108
|
+
project_id: z.string().optional(),
|
|
109
|
+
content: z.string(),
|
|
110
|
+
// attachment: z.object({}).optional(),
|
|
111
|
+
},
|
|
112
|
+
method: 'POST',
|
|
113
|
+
path: '/comments',
|
|
114
|
+
errorPrefix: 'Failed to create comment',
|
|
115
|
+
}),
|
|
116
|
+
get_comment: createApiHandler({
|
|
117
|
+
schemaShape: {
|
|
118
|
+
id: z.string(),
|
|
119
|
+
},
|
|
120
|
+
method: 'GET',
|
|
121
|
+
path: '/comments/{id}',
|
|
122
|
+
errorPrefix: 'Failed to get comment',
|
|
123
|
+
}),
|
|
124
|
+
update_comment: createApiHandler({
|
|
125
|
+
schemaShape: {
|
|
126
|
+
id: z.string(),
|
|
127
|
+
content: z.string(),
|
|
128
|
+
},
|
|
129
|
+
method: 'POST',
|
|
130
|
+
path: '/comments/{id}',
|
|
131
|
+
errorPrefix: 'Failed to update comment',
|
|
132
|
+
}),
|
|
133
|
+
delete_comment: createApiHandler({
|
|
134
|
+
schemaShape: {
|
|
135
|
+
id: z.string(),
|
|
136
|
+
},
|
|
137
|
+
method: 'DELETE',
|
|
138
|
+
path: '/comments/{id}',
|
|
139
|
+
errorPrefix: 'Failed to delete comment',
|
|
140
|
+
}),
|
|
141
|
+
};
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { log, todoistApi } from '../utils/helpers.js';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
export const CREATE_TASK_TOOLS = [
|
|
4
|
+
{
|
|
5
|
+
name: 'create_task',
|
|
6
|
+
description: 'Create a new task in Todoist with various parameters',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
content: {
|
|
11
|
+
type: "string",
|
|
12
|
+
description: 'Task content. This value may contain markdown-formatted text and hyperlinks.',
|
|
13
|
+
},
|
|
14
|
+
description: {
|
|
15
|
+
type: "string",
|
|
16
|
+
description: 'A description for the task. This value may contain markdown-formatted text and hyperlinks.',
|
|
17
|
+
},
|
|
18
|
+
project_id: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: 'Task project ID. If not set, task is put to user\'s Inbox.',
|
|
21
|
+
},
|
|
22
|
+
section_id: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: 'ID of section to put task into.',
|
|
25
|
+
},
|
|
26
|
+
parent_id: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: 'Parent task ID.',
|
|
29
|
+
},
|
|
30
|
+
order: {
|
|
31
|
+
type: "integer",
|
|
32
|
+
description: 'Non-zero integer value used by clients to sort tasks under the same parent.',
|
|
33
|
+
},
|
|
34
|
+
labels: {
|
|
35
|
+
type: "array",
|
|
36
|
+
items: {
|
|
37
|
+
type: "string"
|
|
38
|
+
},
|
|
39
|
+
description: 'The task\'s labels (a list of names that may represent either personal or shared labels).',
|
|
40
|
+
},
|
|
41
|
+
priority: {
|
|
42
|
+
type: "integer",
|
|
43
|
+
description: 'Task priority from 1 (normal) to 4 (urgent).',
|
|
44
|
+
enum: [1, 2, 3, 4]
|
|
45
|
+
},
|
|
46
|
+
due_string: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: 'Human defined task due date (ex.: "next Monday", "Tomorrow"). Value is set using local (not UTC) time.',
|
|
49
|
+
},
|
|
50
|
+
due_date: {
|
|
51
|
+
type: "string",
|
|
52
|
+
description: 'Specific date in YYYY-MM-DD format relative to user\'s timezone.',
|
|
53
|
+
},
|
|
54
|
+
due_datetime: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: 'Specific date and time in RFC3339 format in UTC.',
|
|
57
|
+
},
|
|
58
|
+
due_lang: {
|
|
59
|
+
type: "string",
|
|
60
|
+
description: '2-letter code specifying language in case due_string is not written in English.',
|
|
61
|
+
},
|
|
62
|
+
assignee_id: {
|
|
63
|
+
type: "string",
|
|
64
|
+
description: 'The responsible user ID (only applies to shared tasks).',
|
|
65
|
+
},
|
|
66
|
+
duration: {
|
|
67
|
+
type: "integer",
|
|
68
|
+
description: 'A positive (greater than zero) integer for the amount of duration_unit the task will take.',
|
|
69
|
+
},
|
|
70
|
+
duration_unit: {
|
|
71
|
+
type: "string",
|
|
72
|
+
description: 'The unit of time that the duration field represents. Must be either minute or day.',
|
|
73
|
+
enum: ["minute", "day"]
|
|
74
|
+
},
|
|
75
|
+
deadline_date: {
|
|
76
|
+
type: "string",
|
|
77
|
+
description: 'Specific date in YYYY-MM-DD format relative to user\'s timezone.',
|
|
78
|
+
},
|
|
79
|
+
deadline_lang: {
|
|
80
|
+
type: "string",
|
|
81
|
+
description: '2-letter code specifying language of deadline.',
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
required: ["content"]
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
];
|
|
88
|
+
export const CREATE_TASK_HANDLER = {
|
|
89
|
+
'create_task': async (request) => {
|
|
90
|
+
try {
|
|
91
|
+
const args = createTaskArgsSchema.parse(request.params.arguments);
|
|
92
|
+
const newTask = await handleCreateTask(args);
|
|
93
|
+
return {
|
|
94
|
+
toolResult: {
|
|
95
|
+
content: [{
|
|
96
|
+
type: 'text',
|
|
97
|
+
text: JSON.stringify(newTask, null, 2)
|
|
98
|
+
}],
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
104
|
+
throw new Error(`Failed to create task: ${errorMessage}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const createTaskArgsSchema = z.object({
|
|
109
|
+
content: z.string(),
|
|
110
|
+
description: z.string().optional(),
|
|
111
|
+
project_id: z.string().optional(),
|
|
112
|
+
section_id: z.string().optional(),
|
|
113
|
+
parent_id: z.string().optional(),
|
|
114
|
+
order: z.number().int().optional(),
|
|
115
|
+
labels: z.array(z.string()).optional(),
|
|
116
|
+
priority: z.number().int().min(1).max(4).optional(),
|
|
117
|
+
due_string: z.string().optional(),
|
|
118
|
+
due_date: z.string().optional(),
|
|
119
|
+
due_datetime: z.string().optional(),
|
|
120
|
+
due_lang: z.string().optional(),
|
|
121
|
+
assignee_id: z.string().optional(),
|
|
122
|
+
duration: z.number().int().positive().optional(),
|
|
123
|
+
duration_unit: z.enum(["minute", "day"]).optional(),
|
|
124
|
+
deadline_date: z.string().optional(),
|
|
125
|
+
deadline_lang: z.string().optional()
|
|
126
|
+
});
|
|
127
|
+
async function handleCreateTask(args) {
|
|
128
|
+
log('Creating task with args:', JSON.stringify(args, null, 2));
|
|
129
|
+
try {
|
|
130
|
+
// Create task data object with the parameters
|
|
131
|
+
const taskData = {
|
|
132
|
+
content: args.content
|
|
133
|
+
};
|
|
134
|
+
// Add optional parameters directly using the API field names (snake_case)
|
|
135
|
+
if (args.description)
|
|
136
|
+
taskData.description = args.description;
|
|
137
|
+
if (args.project_id)
|
|
138
|
+
taskData.project_id = args.project_id;
|
|
139
|
+
if (args.section_id)
|
|
140
|
+
taskData.section_id = args.section_id;
|
|
141
|
+
if (args.parent_id)
|
|
142
|
+
taskData.parent_id = args.parent_id;
|
|
143
|
+
if (args.order)
|
|
144
|
+
taskData.order = args.order;
|
|
145
|
+
if (args.labels)
|
|
146
|
+
taskData.labels = args.labels;
|
|
147
|
+
if (args.priority)
|
|
148
|
+
taskData.priority = args.priority;
|
|
149
|
+
if (args.due_string)
|
|
150
|
+
taskData.due_string = args.due_string;
|
|
151
|
+
if (args.due_date)
|
|
152
|
+
taskData.due_date = args.due_date;
|
|
153
|
+
if (args.due_datetime)
|
|
154
|
+
taskData.due_datetime = args.due_datetime;
|
|
155
|
+
if (args.due_lang)
|
|
156
|
+
taskData.due_lang = args.due_lang;
|
|
157
|
+
if (args.assignee_id)
|
|
158
|
+
taskData.assignee_id = args.assignee_id;
|
|
159
|
+
// Handle duration object
|
|
160
|
+
if (args.duration && args.duration_unit) {
|
|
161
|
+
taskData.duration = {
|
|
162
|
+
amount: args.duration,
|
|
163
|
+
unit: args.duration_unit
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
if (args.deadline_date)
|
|
167
|
+
taskData.deadline_date = args.deadline_date;
|
|
168
|
+
if (args.deadline_lang)
|
|
169
|
+
taskData.deadline_lang = args.deadline_lang;
|
|
170
|
+
// Make direct POST request to /tasks endpoint
|
|
171
|
+
const newTask = await todoistApi.post('/tasks', taskData);
|
|
172
|
+
// Return the response directly from the API
|
|
173
|
+
return newTask;
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
log('Error creating task:', error);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ToolHandlers } from '../utils/types.js';
|
|
2
|
+
export declare const EXAMPLE_TOOLS: {
|
|
3
|
+
[x: string]: unknown;
|
|
4
|
+
name: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
[x: string]: unknown;
|
|
7
|
+
type: "object";
|
|
8
|
+
properties?: {
|
|
9
|
+
[x: string]: unknown;
|
|
10
|
+
} | undefined;
|
|
11
|
+
};
|
|
12
|
+
description?: string | undefined;
|
|
13
|
+
}[];
|
|
14
|
+
export declare const EXAMPLE_HANDLERS: ToolHandlers;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { log, todoistApi } from '../utils/helpers.js';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
// Task list tool definition
|
|
4
|
+
const GET_TASKS_TOOL = {
|
|
5
|
+
name: 'todoist_get_tasks',
|
|
6
|
+
description: 'Get a list of tasks from Todoist with various filters',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
project_id: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'Filter tasks by project ID (optional)'
|
|
13
|
+
},
|
|
14
|
+
filter: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Natural language filter like "today", "tomorrow", "next week", "priority 1", "overdue" (optional)'
|
|
17
|
+
},
|
|
18
|
+
priority: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
description: 'Filter by priority level (1-4) (optional)',
|
|
21
|
+
enum: [1, 2, 3, 4]
|
|
22
|
+
},
|
|
23
|
+
limit: {
|
|
24
|
+
type: 'number',
|
|
25
|
+
description: 'Maximum number of tasks to return (optional)',
|
|
26
|
+
default: 10
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
// Export all tools
|
|
32
|
+
export const EXAMPLE_TOOLS = [GET_TASKS_TOOL];
|
|
33
|
+
// Input validation schema using zod
|
|
34
|
+
const getTasksArgsSchema = z.object({
|
|
35
|
+
project_id: z.string().optional(),
|
|
36
|
+
filter: z.string().optional(),
|
|
37
|
+
priority: z.number().min(1).max(4).optional(),
|
|
38
|
+
limit: z.number().positive().optional()
|
|
39
|
+
});
|
|
40
|
+
// Handler function implementation
|
|
41
|
+
async function handleGetTasks(args) {
|
|
42
|
+
const { project_id, filter, priority, limit } = args;
|
|
43
|
+
log('Getting tasks', project_id ? `for project ${project_id}` : '', filter ? `with filter: ${filter}` : '', priority ? `with priority: ${priority}` : '', limit ? `with limit: ${limit}` : '');
|
|
44
|
+
try {
|
|
45
|
+
// Get tasks with filtering from API
|
|
46
|
+
const tasks = await todoistApi.getTasks({
|
|
47
|
+
projectId: project_id,
|
|
48
|
+
filter: filter
|
|
49
|
+
});
|
|
50
|
+
let filteredTasks = tasks.results;
|
|
51
|
+
if (priority) {
|
|
52
|
+
filteredTasks = filteredTasks.filter((task) => task.priority === priority);
|
|
53
|
+
}
|
|
54
|
+
// Apply limit if specified
|
|
55
|
+
if (limit && limit > 0) {
|
|
56
|
+
filteredTasks = filteredTasks.slice(0, limit);
|
|
57
|
+
}
|
|
58
|
+
// Return formatted task data
|
|
59
|
+
return filteredTasks.map(task => ({
|
|
60
|
+
id: task.id,
|
|
61
|
+
content: task.content,
|
|
62
|
+
project_id: task.projectId,
|
|
63
|
+
priority: task.priority,
|
|
64
|
+
due: task.due,
|
|
65
|
+
url: task.url,
|
|
66
|
+
labels: task.labels,
|
|
67
|
+
completed: task.isCompleted,
|
|
68
|
+
created_at: task.createdAt
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
log('Error fetching tasks:', error);
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Export handlers
|
|
77
|
+
export const EXAMPLE_HANDLERS = {
|
|
78
|
+
'todoist_get_tasks': async (request) => {
|
|
79
|
+
try {
|
|
80
|
+
// Parse and validate arguments
|
|
81
|
+
const args = getTasksArgsSchema.parse(request.params.arguments);
|
|
82
|
+
// Get tasks with all filters applied
|
|
83
|
+
const tasks = await handleGetTasks(args);
|
|
84
|
+
// Return formatted response
|
|
85
|
+
return {
|
|
86
|
+
toolResult: {
|
|
87
|
+
content: [{
|
|
88
|
+
type: 'text',
|
|
89
|
+
text: JSON.stringify(tasks, null, 2)
|
|
90
|
+
}],
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
96
|
+
throw new Error(`Failed to get tasks: ${errorMessage}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|