@kydycode/todoist-mcp-server-ext 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Abhiram Nair
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,261 @@
1
+ # Enhanced Todoist MCP Server Extended
2
+ [![smithery badge](https://smithery.ai/badge/@abhiz123/todoist-mcp-server)](https://smithery.ai/server/@abhiz123/todoist-mcp-server)
3
+
4
+ > **Extended Version** - Forked and enhanced by [kydycode](https://github.com/kydycode) from the original [@abhiz123/todoist-mcp-server](https://github.com/abhiz123/todoist-mcp-server)
5
+
6
+ A comprehensive MCP (Model Context Protocol) server implementation that provides full integration between Claude and Todoist. This **extended version** includes additional features, improved compatibility, and enhanced functionality using the complete Todoist API with the latest MCP SDK.
7
+
8
+ <a href="https://glama.ai/mcp/servers/fhaif4fv1w">
9
+ <img width="380" height="200" src="https://glama.ai/mcp/servers/fhaif4fv1w/badge" alt="Todoist Server MCP server" />
10
+ </a>
11
+
12
+ ## 🆕 Extended Version Features
13
+
14
+ ### 🔧 **Technical Improvements**
15
+ * **Updated MCP SDK Compatibility**: Compatible with MCP SDK 0.5.0 and latest versions
16
+ * **Enhanced Error Handling**: Comprehensive error handling with detailed error messages
17
+ * **Improved TypeScript Support**: Better type safety and compatibility
18
+ * **Optimized API Usage**: Efficient use of Todoist API with proper parameter handling
19
+ * **Better Response Formatting**: Enhanced task and project formatting for better readability
20
+
21
+ ### ✨ **Enhanced Task Management**
22
+ * **Direct ID-based Operations**: Efficient task operations using task IDs instead of search
23
+ * **Comprehensive Task Creation**: Support for subtasks, labels, projects, sections, priorities
24
+ * **Quick Add Integration**: Natural language task creation using Todoist's Quick Add
25
+ * **Advanced Task Search**: Content-based search with project scoping
26
+ * **Task Movement Capabilities**: Move tasks between projects, sections, or make them subtasks
27
+ * **Task State Management**: Complete, reopen, and manage task lifecycle
28
+ * **Smart Filtering**: Enhanced filtering with better parameter handling
29
+
30
+ ### 🗂️ **Complete Project Management**
31
+ * **Full Project CRUD**: Create, read, update, delete projects with all properties
32
+ * **Sub-project Support**: Create hierarchical project structures
33
+ * **Project Customization**: Set colors, favorites, view styles (list/board)
34
+ * **Enhanced Project Listing**: Improved project retrieval and formatting
35
+
36
+ ### 📋 **Section Management**
37
+ * **Complete Section Operations**: Create, read, update, delete sections
38
+ * **Project-specific Sections**: Filter and manage sections within projects
39
+ * **Section Organization**: Proper ordering and structure management
40
+
41
+ ## 🛠️ Available Tools
42
+
43
+ ### Task Operations (10 tools)
44
+ | Tool | Description | Enhanced Features |
45
+ |------|-------------|------------------|
46
+ | `todoist_create_task` | Create tasks with full options | ✅ Subtasks, labels, projects, sections, priorities |
47
+ | `todoist_quick_add_task` | Natural language task creation | ✅ Todoist Quick Add syntax support |
48
+ | `todoist_get_tasks` | Retrieve tasks with filtering | ✅ Project, section, parent, label filtering |
49
+ | `todoist_get_task` | Get specific task by ID | ✅ Direct ID-based retrieval |
50
+ | `todoist_update_task` | Update task properties | ✅ Content, description, due date, priority, labels |
51
+ | `todoist_delete_task` | Delete task by ID | ✅ Direct deletion with confirmation |
52
+ | `todoist_complete_task` | Mark task complete | ✅ Instant completion |
53
+ | `todoist_reopen_task` | Reopen completed task | ✅ Task restoration |
54
+ | `todoist_search_tasks` | Search tasks by content | ✅ Project-scoped content search |
55
+
56
+ ### Project Operations (5 tools)
57
+ | Tool | Description | Enhanced Features |
58
+ |------|-------------|------------------|
59
+ | `todoist_get_projects` | List all active projects | ✅ Clean formatting, proper error handling |
60
+ | `todoist_get_project` | Get specific project by ID | ✅ Direct project retrieval |
61
+ | `todoist_create_project` | Create new project | ✅ Name, color, favorite, view style, sub-projects |
62
+ | `todoist_update_project` | Update project properties | ✅ All project attributes |
63
+ | `todoist_delete_project` | Delete project by ID | ✅ Safe deletion with confirmation |
64
+
65
+ ### Section Operations (4 tools)
66
+ | Tool | Description | Enhanced Features |
67
+ |------|-------------|------------------|
68
+ | `todoist_get_sections` | List sections | ✅ All sections or project-specific |
69
+ | `todoist_create_section` | Create section in project | ✅ Name, project, ordering |
70
+ | `todoist_update_section` | Update section name | ✅ Direct section modification |
71
+ | `todoist_delete_section` | Delete section by ID | ✅ Clean section removal |
72
+
73
+ ## 🚀 Installation & Setup
74
+
75
+ ### Local Development Setup
76
+
77
+ ```bash
78
+ # Clone the extended repository
79
+ git clone https://github.com/kydycode/todoist-mcp-server-ext.git
80
+ cd todoist-mcp-server-ext
81
+
82
+ # Install dependencies
83
+ npm install
84
+
85
+ # Build the project
86
+ npm run build
87
+ ```
88
+
89
+ ### Getting a Todoist API Token
90
+ 1. Log in to your Todoist account
91
+ 2. Navigate to Settings → Integrations → Developer
92
+ 3. Copy your API token
93
+
94
+ ### Usage with Claude Desktop
95
+
96
+ Add to your `claude_desktop_config.json`:
97
+
98
+ #### Option 1: Run locally built version
99
+ ```json
100
+ {
101
+ "mcpServers": {
102
+ "todoist-mcp-server": {
103
+ "command": "node",
104
+ "args": ["/path/to/todoist-mcp-server-ext/dist/index.js"],
105
+ "env": {
106
+ "TODOIST_API_TOKEN": "your_api_token_here"
107
+ }
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ #### Option 2: Run via npm (if installed globally)
114
+ ```json
115
+ {
116
+ "mcpServers": {
117
+ "todoist-mcp-server": {
118
+ "command": "npx",
119
+ "args": ["-y", "@kydycode/todoist-mcp-server-ext"],
120
+ "env": {
121
+ "TODOIST_API_TOKEN": "your_api_token_here"
122
+ }
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ #### Option 3: Install globally first
129
+ ```bash
130
+ # Install the extended version globally
131
+ npm install -g @kydycode/todoist-mcp-server-ext
132
+
133
+ # Then use in Claude Desktop config
134
+ {
135
+ "mcpServers": {
136
+ "todoist-mcp-server": {
137
+ "command": "todoist-mcp-server-ext",
138
+ "env": {
139
+ "TODOIST_API_TOKEN": "your_api_token_here"
140
+ }
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## 📖 Usage Examples
147
+
148
+ ### 🎯 Advanced Task Creation
149
+ ```
150
+ "Create task 'Team Meeting' in project 'Work'"
151
+ "Add high priority task 'Fix critical bug' with labels 'urgent' and 'backend'"
152
+ "Quick add: 'Buy milk tomorrow at 2pm #shopping !p1'"
153
+ "Create subtask 'Prepare agenda' under existing task"
154
+ ```
155
+
156
+ ### 🔍 Task Management & Search
157
+ ```
158
+ "Get all tasks in project 'Work'"
159
+ "Search for tasks containing 'meeting'"
160
+ "Show task details for specific task ID"
161
+ "Update task priority to urgent"
162
+ "Complete task and then reopen it"
163
+ ```
164
+
165
+ ### 🗂️ Project Organization
166
+ ```
167
+ "List all my projects"
168
+ "Create project 'Q1 Planning' with blue color and board view"
169
+ "Create sub-project 'Marketing' under 'Business'"
170
+ "Update project to favorite"
171
+ "Get specific project details"
172
+ ```
173
+
174
+ ### 📋 Section Management
175
+ ```
176
+ "Create section 'In Progress' in Development project"
177
+ "List all sections in Work project"
178
+ "Update section name to 'Completed'"
179
+ "Delete empty section"
180
+ ```
181
+
182
+ ## 🆚 Extended vs Original Comparison
183
+
184
+ | Feature | Original | Extended Version |
185
+ |---------|----------|------------------|
186
+ | **MCP SDK Compatibility** | Older version | ✅ Latest MCP SDK 0.5.0+ |
187
+ | **Error Handling** | Basic | ✅ Comprehensive with detailed messages |
188
+ | **TypeScript Support** | Limited | ✅ Full type safety |
189
+ | **Task Operations** | Search-based | ✅ Direct ID-based + Search fallback |
190
+ | **Project Management** | Limited | ✅ Full CRUD operations |
191
+ | **Section Management** | Basic | ✅ Complete section operations |
192
+ | **API Parameter Handling** | Inconsistent | ✅ Proper parameter validation |
193
+ | **Response Formatting** | Basic | ✅ Enhanced readability |
194
+ | **Build System** | Issues | ✅ Clean compilation |
195
+
196
+ ## 🔧 Development
197
+
198
+ ### Project Structure
199
+ ```
200
+ src/
201
+ ├── index.ts # Main server implementation with all tools
202
+ ├── package.json # Dependencies and scripts
203
+ ├── tsconfig.json # TypeScript configuration
204
+ └── dist/ # Compiled JavaScript output
205
+ ├── index.js
206
+ └── index.d.ts
207
+ ```
208
+
209
+ ### Building from Source
210
+ ```bash
211
+ # Install dependencies
212
+ npm install
213
+
214
+ # Build TypeScript
215
+ npm run build
216
+
217
+ # Test the server (requires TODOIST_API_TOKEN)
218
+ TODOIST_API_TOKEN=your_token node dist/index.js
219
+ ```
220
+
221
+ ### Development Scripts
222
+ ```bash
223
+ npm run build # Compile TypeScript
224
+ npm run watch # Watch for changes and rebuild
225
+ npm run prepare # Pre-publish build
226
+ ```
227
+
228
+ ## 🤝 Contributing
229
+
230
+ Contributions are welcome! This extended version accepts contributions for:
231
+
232
+ - Additional Todoist API endpoints
233
+ - Enhanced error handling and validation
234
+ - Performance optimizations
235
+ - Documentation improvements
236
+ - Bug fixes and compatibility updates
237
+
238
+ Please submit issues and pull requests to the [extended repository](https://github.com/kydycode/todoist-mcp-server-ext).
239
+
240
+ ## 📄 License
241
+
242
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
243
+
244
+ ## 🙏 Credits
245
+
246
+ - **Extended Version**: [kydycode](https://github.com/kydycode) - Enhanced functionality and compatibility
247
+ - **Original Creator**: [@abhiz123](https://github.com/abhiz123) - Initial Todoist MCP server implementation
248
+ - **MCP Protocol**: [Model Context Protocol](https://modelcontextprotocol.io/) by Anthropic
249
+
250
+ ## 🐛 Issues and Support
251
+
252
+ - **Extended Version Issues**: [GitHub Issues](https://github.com/kydycode/todoist-mcp-server-ext/issues)
253
+ - **Original Repository**: [abhiz123/todoist-mcp-server](https://github.com/abhiz123/todoist-mcp-server)
254
+
255
+ ## 🔗 Related Links
256
+
257
+ - **Extended Repository**: [kydycode/todoist-mcp-server-ext](https://github.com/kydycode/todoist-mcp-server-ext)
258
+ - **Original Repository**: [abhiz123/todoist-mcp-server](https://github.com/abhiz123/todoist-mcp-server)
259
+ - [Todoist API Documentation](https://developer.todoist.com/)
260
+ - [Model Context Protocol](https://modelcontextprotocol.io/)
261
+ - [Claude Desktop](https://claude.ai/download)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,918 @@
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 { TodoistApi } from "@doist/todoist-api-typescript";
6
+ // Check for API token
7
+ const TODOIST_API_TOKEN = process.env.TODOIST_API_TOKEN;
8
+ if (!TODOIST_API_TOKEN) {
9
+ console.error("Error: TODOIST_API_TOKEN environment variable is required");
10
+ process.exit(1);
11
+ }
12
+ // Initialize Todoist client
13
+ const todoistClient = new TodoistApi(TODOIST_API_TOKEN);
14
+ // Enhanced Task Tools
15
+ const CREATE_TASK_TOOL = {
16
+ name: "todoist_create_task",
17
+ description: "Create a new task in Todoist with comprehensive options including subtasks",
18
+ inputSchema: {
19
+ type: "object",
20
+ properties: {
21
+ content: {
22
+ type: "string",
23
+ description: "The content/title of the task"
24
+ },
25
+ description: {
26
+ type: "string",
27
+ description: "Detailed description of the task (optional)"
28
+ },
29
+ projectId: {
30
+ type: "string",
31
+ description: "Project ID to create the task in (optional)"
32
+ },
33
+ sectionId: {
34
+ type: "string",
35
+ description: "Section ID to create the task in (optional)"
36
+ },
37
+ parentId: {
38
+ type: "string",
39
+ description: "Parent task ID to create this as a subtask (optional)"
40
+ },
41
+ dueString: {
42
+ type: "string",
43
+ description: "Natural language due date like 'tomorrow', 'next Monday', 'Jan 23' (optional)"
44
+ },
45
+ priority: {
46
+ type: "number",
47
+ description: "Task priority from 1 (normal) to 4 (urgent) (optional)",
48
+ enum: [1, 2, 3, 4]
49
+ },
50
+ labels: {
51
+ type: "array",
52
+ items: { type: "string" },
53
+ description: "Array of label names to assign to the task (optional)"
54
+ }
55
+ },
56
+ required: ["content"]
57
+ }
58
+ };
59
+ const QUICK_ADD_TASK_TOOL = {
60
+ name: "todoist_quick_add_task",
61
+ description: "Create a task using Todoist's Quick Add feature with natural language parsing",
62
+ inputSchema: {
63
+ type: "object",
64
+ properties: {
65
+ text: {
66
+ type: "string",
67
+ description: "Natural language text for quick task creation (e.g., 'Buy milk tomorrow at 2pm #shopping')"
68
+ },
69
+ note: {
70
+ type: "string",
71
+ description: "Additional note for the task (optional)"
72
+ },
73
+ reminder: {
74
+ type: "string",
75
+ description: "Reminder time (optional)"
76
+ }
77
+ },
78
+ required: ["text"]
79
+ }
80
+ };
81
+ const GET_TASKS_TOOL = {
82
+ name: "todoist_get_tasks",
83
+ description: "Get tasks with comprehensive filtering and pagination support",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ projectId: {
88
+ type: "string",
89
+ description: "Filter tasks by project ID (optional)"
90
+ },
91
+ sectionId: {
92
+ type: "string",
93
+ description: "Filter tasks by section ID (optional)"
94
+ },
95
+ parentId: {
96
+ type: "string",
97
+ description: "Filter tasks by parent ID (get subtasks) (optional)"
98
+ },
99
+ label: {
100
+ type: "string",
101
+ description: "Filter tasks by label name (optional)"
102
+ },
103
+ ids: {
104
+ type: "array",
105
+ items: { type: "string" },
106
+ description: "Array of task IDs to retrieve (optional)"
107
+ },
108
+ cursor: {
109
+ type: "string",
110
+ description: "Pagination cursor for next page (optional)"
111
+ },
112
+ limit: {
113
+ type: "number",
114
+ description: "Maximum number of tasks to return (default: 50, max: 200) (optional)",
115
+ default: 50
116
+ }
117
+ }
118
+ }
119
+ };
120
+ const GET_TASK_TOOL = {
121
+ name: "todoist_get_task",
122
+ description: "Get a specific task by its ID",
123
+ inputSchema: {
124
+ type: "object",
125
+ properties: {
126
+ taskId: {
127
+ type: "string",
128
+ description: "The ID of the task to retrieve"
129
+ }
130
+ },
131
+ required: ["taskId"]
132
+ }
133
+ };
134
+ const UPDATE_TASK_TOOL = {
135
+ name: "todoist_update_task",
136
+ description: "Update an existing task by its ID",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ taskId: {
141
+ type: "string",
142
+ description: "The ID of the task to update"
143
+ },
144
+ content: {
145
+ type: "string",
146
+ description: "New content/title for the task (optional)"
147
+ },
148
+ description: {
149
+ type: "string",
150
+ description: "New description for the task (optional)"
151
+ },
152
+ dueString: {
153
+ type: "string",
154
+ description: "New due date in natural language (optional)"
155
+ },
156
+ priority: {
157
+ type: "number",
158
+ description: "New priority level from 1 (normal) to 4 (urgent) (optional)",
159
+ enum: [1, 2, 3, 4]
160
+ },
161
+ labels: {
162
+ type: "array",
163
+ items: { type: "string" },
164
+ description: "New array of label names (optional)"
165
+ }
166
+ },
167
+ required: ["taskId"]
168
+ }
169
+ };
170
+ const DELETE_TASK_TOOL = {
171
+ name: "todoist_delete_task",
172
+ description: "Delete a task by its ID",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: {
176
+ taskId: {
177
+ type: "string",
178
+ description: "The ID of the task to delete"
179
+ }
180
+ },
181
+ required: ["taskId"]
182
+ }
183
+ };
184
+ const COMPLETE_TASK_TOOL = {
185
+ name: "todoist_complete_task",
186
+ description: "Mark a task as complete by its ID",
187
+ inputSchema: {
188
+ type: "object",
189
+ properties: {
190
+ taskId: {
191
+ type: "string",
192
+ description: "The ID of the task to complete"
193
+ }
194
+ },
195
+ required: ["taskId"]
196
+ }
197
+ };
198
+ const REOPEN_TASK_TOOL = {
199
+ name: "todoist_reopen_task",
200
+ description: "Reopen a completed task by its ID",
201
+ inputSchema: {
202
+ type: "object",
203
+ properties: {
204
+ taskId: {
205
+ type: "string",
206
+ description: "The ID of the completed task to reopen"
207
+ }
208
+ },
209
+ required: ["taskId"]
210
+ }
211
+ };
212
+ // Project Management Tools
213
+ const GET_PROJECTS_TOOL = {
214
+ name: "todoist_get_projects",
215
+ description: "Get all active projects with pagination support",
216
+ inputSchema: {
217
+ type: "object",
218
+ properties: {
219
+ cursor: {
220
+ type: "string",
221
+ description: "Pagination cursor for next page (optional)"
222
+ },
223
+ limit: {
224
+ type: "number",
225
+ description: "Maximum number of projects to return (default: 50, max: 200) (optional)",
226
+ default: 50
227
+ }
228
+ }
229
+ }
230
+ };
231
+ const GET_PROJECT_TOOL = {
232
+ name: "todoist_get_project",
233
+ description: "Get a specific project by its ID",
234
+ inputSchema: {
235
+ type: "object",
236
+ properties: {
237
+ projectId: {
238
+ type: "string",
239
+ description: "The ID of the project to retrieve"
240
+ }
241
+ },
242
+ required: ["projectId"]
243
+ }
244
+ };
245
+ const CREATE_PROJECT_TOOL = {
246
+ name: "todoist_create_project",
247
+ description: "Create a new project",
248
+ inputSchema: {
249
+ type: "object",
250
+ properties: {
251
+ name: {
252
+ type: "string",
253
+ description: "The name of the project"
254
+ },
255
+ parentId: {
256
+ type: "string",
257
+ description: "Parent project ID for creating a sub-project (optional)"
258
+ },
259
+ color: {
260
+ type: "string",
261
+ description: "Project color (optional)"
262
+ },
263
+ isFavorite: {
264
+ type: "boolean",
265
+ description: "Whether to mark as favorite (optional)"
266
+ },
267
+ viewStyle: {
268
+ type: "string",
269
+ description: "Project view style: 'list' or 'board' (optional)",
270
+ enum: ["list", "board"]
271
+ }
272
+ },
273
+ required: ["name"]
274
+ }
275
+ };
276
+ const UPDATE_PROJECT_TOOL = {
277
+ name: "todoist_update_project",
278
+ description: "Update an existing project",
279
+ inputSchema: {
280
+ type: "object",
281
+ properties: {
282
+ projectId: {
283
+ type: "string",
284
+ description: "The ID of the project to update"
285
+ },
286
+ name: {
287
+ type: "string",
288
+ description: "New name for the project (optional)"
289
+ },
290
+ color: {
291
+ type: "string",
292
+ description: "New color for the project (optional)"
293
+ },
294
+ isFavorite: {
295
+ type: "boolean",
296
+ description: "Whether to mark as favorite (optional)"
297
+ },
298
+ viewStyle: {
299
+ type: "string",
300
+ description: "Project view style: 'list' or 'board' (optional)",
301
+ enum: ["list", "board"]
302
+ }
303
+ },
304
+ required: ["projectId"]
305
+ }
306
+ };
307
+ const DELETE_PROJECT_TOOL = {
308
+ name: "todoist_delete_project",
309
+ description: "Delete a project by its ID",
310
+ inputSchema: {
311
+ type: "object",
312
+ properties: {
313
+ projectId: {
314
+ type: "string",
315
+ description: "The ID of the project to delete"
316
+ }
317
+ },
318
+ required: ["projectId"]
319
+ }
320
+ };
321
+ // Section Management Tools
322
+ const GET_SECTIONS_TOOL = {
323
+ name: "todoist_get_sections",
324
+ description: "Get all sections or sections for a specific project",
325
+ inputSchema: {
326
+ type: "object",
327
+ properties: {
328
+ projectId: {
329
+ type: "string",
330
+ description: "Filter sections by project ID (optional)"
331
+ }
332
+ }
333
+ }
334
+ };
335
+ const CREATE_SECTION_TOOL = {
336
+ name: "todoist_create_section",
337
+ description: "Create a new section in a project",
338
+ inputSchema: {
339
+ type: "object",
340
+ properties: {
341
+ name: {
342
+ type: "string",
343
+ description: "The name of the section"
344
+ },
345
+ projectId: {
346
+ type: "string",
347
+ description: "The project ID where the section will be created"
348
+ },
349
+ order: {
350
+ type: "number",
351
+ description: "Order of the section (optional)"
352
+ }
353
+ },
354
+ required: ["name", "projectId"]
355
+ }
356
+ };
357
+ const UPDATE_SECTION_TOOL = {
358
+ name: "todoist_update_section",
359
+ description: "Update an existing section",
360
+ inputSchema: {
361
+ type: "object",
362
+ properties: {
363
+ sectionId: {
364
+ type: "string",
365
+ description: "The ID of the section to update"
366
+ },
367
+ name: {
368
+ type: "string",
369
+ description: "New name for the section"
370
+ }
371
+ },
372
+ required: ["sectionId", "name"]
373
+ }
374
+ };
375
+ const DELETE_SECTION_TOOL = {
376
+ name: "todoist_delete_section",
377
+ description: "Delete a section by its ID",
378
+ inputSchema: {
379
+ type: "object",
380
+ properties: {
381
+ sectionId: {
382
+ type: "string",
383
+ description: "The ID of the section to delete"
384
+ }
385
+ },
386
+ required: ["sectionId"]
387
+ }
388
+ };
389
+ // Search Tool
390
+ const SEARCH_TASKS_TOOL = {
391
+ name: "todoist_search_tasks",
392
+ description: "Search for tasks by content/name (fallback for when ID is not known)",
393
+ inputSchema: {
394
+ type: "object",
395
+ properties: {
396
+ query: {
397
+ type: "string",
398
+ description: "Search query to find tasks by content"
399
+ },
400
+ projectId: {
401
+ type: "string",
402
+ description: "Limit search to specific project (optional)"
403
+ },
404
+ limit: {
405
+ type: "number",
406
+ description: "Maximum number of results (default: 10) (optional)",
407
+ default: 10
408
+ }
409
+ },
410
+ required: ["query"]
411
+ }
412
+ };
413
+ // Server implementation
414
+ const server = new Server({
415
+ name: "todoist-mcp-server-enhanced",
416
+ version: "0.2.0",
417
+ }, {
418
+ capabilities: {
419
+ tools: {},
420
+ },
421
+ });
422
+ // Helper function to format task output
423
+ function formatTask(task) {
424
+ return `- ${task.content}${task.description ? `\n Description: ${task.description}` : ''}${task.due ? `\n Due: ${task.due.string}` : ''}${task.priority && task.priority > 1 ? `\n Priority: ${task.priority}` : ''}${task.labels && task.labels.length > 0 ? `\n Labels: ${task.labels.join(', ')}` : ''}${task.parentId ? `\n Parent: ${task.parentId}` : ''}`;
425
+ }
426
+ // Helper function to format project output
427
+ function formatProject(project) {
428
+ return `- ${project.name}${project.color ? `\n Color: ${project.color}` : ''}${project.isFavorite ? `\n Favorite: Yes` : ''}${project.viewStyle ? `\n View: ${project.viewStyle}` : ''}${project.parentId ? `\n Parent: ${project.parentId}` : ''}`;
429
+ }
430
+ // Type guards for arguments
431
+ function isCreateTaskArgs(args) {
432
+ return (typeof args === "object" &&
433
+ args !== null &&
434
+ "content" in args &&
435
+ typeof args.content === "string");
436
+ }
437
+ function isQuickAddArgs(args) {
438
+ return (typeof args === "object" &&
439
+ args !== null &&
440
+ "text" in args &&
441
+ typeof args.text === "string");
442
+ }
443
+ function isGetTasksArgs(args) {
444
+ return typeof args === "object" && args !== null;
445
+ }
446
+ function isTaskIdArgs(args) {
447
+ return (typeof args === "object" &&
448
+ args !== null &&
449
+ "taskId" in args &&
450
+ typeof args.taskId === "string");
451
+ }
452
+ function isUpdateTaskArgs(args) {
453
+ return (typeof args === "object" &&
454
+ args !== null &&
455
+ "taskId" in args &&
456
+ typeof args.taskId === "string");
457
+ }
458
+ function isProjectArgs(args) {
459
+ return typeof args === "object" && args !== null;
460
+ }
461
+ function isProjectIdArgs(args) {
462
+ return (typeof args === "object" &&
463
+ args !== null &&
464
+ "projectId" in args &&
465
+ typeof args.projectId === "string");
466
+ }
467
+ function isCreateProjectArgs(args) {
468
+ return (typeof args === "object" &&
469
+ args !== null &&
470
+ "name" in args &&
471
+ typeof args.name === "string");
472
+ }
473
+ function isUpdateProjectArgs(args) {
474
+ return (typeof args === "object" &&
475
+ args !== null &&
476
+ "projectId" in args &&
477
+ typeof args.projectId === "string");
478
+ }
479
+ function isSectionArgs(args) {
480
+ return typeof args === "object" && args !== null;
481
+ }
482
+ function isCreateSectionArgs(args) {
483
+ return (typeof args === "object" &&
484
+ args !== null &&
485
+ "name" in args &&
486
+ "projectId" in args &&
487
+ typeof args.name === "string" &&
488
+ typeof args.projectId === "string");
489
+ }
490
+ function isUpdateSectionArgs(args) {
491
+ return (typeof args === "object" &&
492
+ args !== null &&
493
+ "sectionId" in args &&
494
+ "name" in args &&
495
+ typeof args.sectionId === "string" &&
496
+ typeof args.name === "string");
497
+ }
498
+ function isSectionIdArgs(args) {
499
+ return (typeof args === "object" &&
500
+ args !== null &&
501
+ "sectionId" in args &&
502
+ typeof args.sectionId === "string");
503
+ }
504
+ function isSearchTasksArgs(args) {
505
+ return (typeof args === "object" &&
506
+ args !== null &&
507
+ "query" in args &&
508
+ typeof args.query === "string");
509
+ }
510
+ // Tool handlers
511
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
512
+ tools: [
513
+ // Task tools
514
+ CREATE_TASK_TOOL,
515
+ QUICK_ADD_TASK_TOOL,
516
+ GET_TASKS_TOOL,
517
+ GET_TASK_TOOL,
518
+ UPDATE_TASK_TOOL,
519
+ DELETE_TASK_TOOL,
520
+ COMPLETE_TASK_TOOL,
521
+ REOPEN_TASK_TOOL,
522
+ SEARCH_TASKS_TOOL,
523
+ // Project tools
524
+ GET_PROJECTS_TOOL,
525
+ GET_PROJECT_TOOL,
526
+ CREATE_PROJECT_TOOL,
527
+ UPDATE_PROJECT_TOOL,
528
+ DELETE_PROJECT_TOOL,
529
+ // Section tools
530
+ GET_SECTIONS_TOOL,
531
+ CREATE_SECTION_TOOL,
532
+ UPDATE_SECTION_TOOL,
533
+ DELETE_SECTION_TOOL,
534
+ ],
535
+ }));
536
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
537
+ try {
538
+ const { name, arguments: args } = request.params;
539
+ if (!args) {
540
+ throw new Error("No arguments provided");
541
+ }
542
+ // Task operations
543
+ if (name === "todoist_create_task") {
544
+ if (!isCreateTaskArgs(args)) {
545
+ throw new Error("Invalid arguments for todoist_create_task");
546
+ }
547
+ const taskData = {
548
+ content: args.content,
549
+ };
550
+ if (args.description)
551
+ taskData.description = args.description;
552
+ if (args.projectId)
553
+ taskData.projectId = args.projectId;
554
+ if (args.sectionId)
555
+ taskData.sectionId = args.sectionId;
556
+ if (args.parentId)
557
+ taskData.parentId = args.parentId;
558
+ if (args.dueString)
559
+ taskData.dueString = args.dueString;
560
+ if (args.priority)
561
+ taskData.priority = args.priority;
562
+ if (args.labels && args.labels.length > 0)
563
+ taskData.labels = args.labels;
564
+ const task = await todoistClient.addTask(taskData);
565
+ return {
566
+ content: [{
567
+ type: "text",
568
+ text: `Task created successfully:\nID: ${task.id}\n${formatTask(task)}`
569
+ }],
570
+ isError: false,
571
+ };
572
+ }
573
+ if (name === "todoist_quick_add_task") {
574
+ if (!isQuickAddArgs(args)) {
575
+ throw new Error("Invalid arguments for todoist_quick_add_task");
576
+ }
577
+ const quickAddData = { text: args.text };
578
+ if (args.note)
579
+ quickAddData.note = args.note;
580
+ if (args.reminder)
581
+ quickAddData.reminder = args.reminder;
582
+ const result = await todoistClient.quickAddTask(quickAddData);
583
+ return {
584
+ content: [{
585
+ type: "text",
586
+ text: `Task created via Quick Add:\n${JSON.stringify(result, null, 2)}`
587
+ }],
588
+ isError: false,
589
+ };
590
+ }
591
+ if (name === "todoist_get_tasks") {
592
+ if (!isGetTasksArgs(args)) {
593
+ throw new Error("Invalid arguments for todoist_get_tasks");
594
+ }
595
+ const params = {};
596
+ if (args.projectId)
597
+ params.projectId = args.projectId;
598
+ if (args.sectionId)
599
+ params.sectionId = args.sectionId;
600
+ if (args.parentId)
601
+ params.parentId = args.parentId;
602
+ if (args.label)
603
+ params.label = args.label;
604
+ if (args.ids && args.ids.length > 0)
605
+ params.ids = args.ids;
606
+ if (args.cursor)
607
+ params.cursor = args.cursor;
608
+ if (args.limit)
609
+ params.limit = args.limit;
610
+ const tasks = await todoistClient.getTasks(Object.keys(params).length > 0 ? params : {});
611
+ // Handle both array and paginated response formats
612
+ let taskList;
613
+ let nextCursor = '';
614
+ if (Array.isArray(tasks)) {
615
+ taskList = tasks.map(formatTask).join('\n\n');
616
+ }
617
+ else {
618
+ const paginatedTasks = tasks;
619
+ taskList = paginatedTasks.results?.map(formatTask).join('\n\n') || 'No tasks found';
620
+ nextCursor = paginatedTasks.nextCursor ? `\n\nNext cursor: ${paginatedTasks.nextCursor}` : '';
621
+ }
622
+ return {
623
+ content: [{
624
+ type: "text",
625
+ text: `Tasks:\n${taskList}${nextCursor}`
626
+ }],
627
+ isError: false,
628
+ };
629
+ }
630
+ if (name === "todoist_get_task") {
631
+ if (!isTaskIdArgs(args)) {
632
+ throw new Error("Invalid arguments for todoist_get_task");
633
+ }
634
+ const task = await todoistClient.getTask(args.taskId);
635
+ return {
636
+ content: [{
637
+ type: "text",
638
+ text: `Task details:\nID: ${task.id}\n${formatTask(task)}`
639
+ }],
640
+ isError: false,
641
+ };
642
+ }
643
+ if (name === "todoist_update_task") {
644
+ if (!isUpdateTaskArgs(args)) {
645
+ throw new Error("Invalid arguments for todoist_update_task");
646
+ }
647
+ const updateData = {};
648
+ if (args.content)
649
+ updateData.content = args.content;
650
+ if (args.description)
651
+ updateData.description = args.description;
652
+ if (args.dueString)
653
+ updateData.dueString = args.dueString;
654
+ if (args.priority)
655
+ updateData.priority = args.priority;
656
+ if (args.labels)
657
+ updateData.labels = args.labels;
658
+ const updatedTask = await todoistClient.updateTask(args.taskId, updateData);
659
+ return {
660
+ content: [{
661
+ type: "text",
662
+ text: `Task updated successfully:\nID: ${updatedTask.id}\n${formatTask(updatedTask)}`
663
+ }],
664
+ isError: false,
665
+ };
666
+ }
667
+ if (name === "todoist_delete_task") {
668
+ if (!isTaskIdArgs(args)) {
669
+ throw new Error("Invalid arguments for todoist_delete_task");
670
+ }
671
+ await todoistClient.deleteTask(args.taskId);
672
+ return {
673
+ content: [{
674
+ type: "text",
675
+ text: `Task ${args.taskId} deleted successfully`
676
+ }],
677
+ isError: false,
678
+ };
679
+ }
680
+ if (name === "todoist_complete_task") {
681
+ if (!isTaskIdArgs(args)) {
682
+ throw new Error("Invalid arguments for todoist_complete_task");
683
+ }
684
+ await todoistClient.closeTask(args.taskId);
685
+ return {
686
+ content: [{
687
+ type: "text",
688
+ text: `Task ${args.taskId} completed successfully`
689
+ }],
690
+ isError: false,
691
+ };
692
+ }
693
+ if (name === "todoist_reopen_task") {
694
+ if (!isTaskIdArgs(args)) {
695
+ throw new Error("Invalid arguments for todoist_reopen_task");
696
+ }
697
+ await todoistClient.reopenTask(args.taskId);
698
+ return {
699
+ content: [{
700
+ type: "text",
701
+ text: `Task ${args.taskId} reopened successfully`
702
+ }],
703
+ isError: false,
704
+ };
705
+ }
706
+ if (name === "todoist_search_tasks") {
707
+ if (!isSearchTasksArgs(args)) {
708
+ throw new Error("Invalid arguments for todoist_search_tasks");
709
+ }
710
+ const params = {};
711
+ if (args.projectId)
712
+ params.projectId = args.projectId;
713
+ const tasks = await todoistClient.getTasks(params);
714
+ const allTasks = Array.isArray(tasks) ? tasks : tasks.results || [];
715
+ const matchingTasks = allTasks.filter((task) => task.content.toLowerCase().includes(args.query.toLowerCase())).slice(0, args.limit || 10);
716
+ if (matchingTasks.length === 0) {
717
+ return {
718
+ content: [{
719
+ type: "text",
720
+ text: `No tasks found matching "${args.query}"`
721
+ }],
722
+ isError: false,
723
+ };
724
+ }
725
+ const taskList = matchingTasks.map((task) => `ID: ${task.id}\n${formatTask(task)}`).join('\n\n');
726
+ return {
727
+ content: [{
728
+ type: "text",
729
+ text: `Found ${matchingTasks.length} task(s) matching "${args.query}":\n\n${taskList}`
730
+ }],
731
+ isError: false,
732
+ };
733
+ }
734
+ // Project operations
735
+ if (name === "todoist_get_projects") {
736
+ if (!isProjectArgs(args)) {
737
+ throw new Error("Invalid arguments for todoist_get_projects");
738
+ }
739
+ const params = {};
740
+ if (args.cursor)
741
+ params.cursor = args.cursor;
742
+ if (args.limit)
743
+ params.limit = args.limit;
744
+ // Note: getProjects() may not accept parameters in this API version
745
+ const projects = await todoistClient.getProjects();
746
+ // Handle simple array response
747
+ const projectList = Array.isArray(projects)
748
+ ? projects.map(formatProject).join('\n\n')
749
+ : 'No projects found';
750
+ return {
751
+ content: [{
752
+ type: "text",
753
+ text: `Projects:\n${projectList}`
754
+ }],
755
+ isError: false,
756
+ };
757
+ }
758
+ if (name === "todoist_get_project") {
759
+ if (!isProjectIdArgs(args)) {
760
+ throw new Error("Invalid arguments for todoist_get_project");
761
+ }
762
+ const project = await todoistClient.getProject(args.projectId);
763
+ return {
764
+ content: [{
765
+ type: "text",
766
+ text: `Project details:\nID: ${project.id}\n${formatProject(project)}`
767
+ }],
768
+ isError: false,
769
+ };
770
+ }
771
+ if (name === "todoist_create_project") {
772
+ if (!isCreateProjectArgs(args)) {
773
+ throw new Error("Invalid arguments for todoist_create_project");
774
+ }
775
+ const projectData = { name: args.name };
776
+ if (args.parentId)
777
+ projectData.parentId = args.parentId;
778
+ if (args.color)
779
+ projectData.color = args.color;
780
+ if (args.isFavorite !== undefined)
781
+ projectData.isFavorite = args.isFavorite;
782
+ if (args.viewStyle)
783
+ projectData.viewStyle = args.viewStyle;
784
+ const project = await todoistClient.addProject(projectData);
785
+ return {
786
+ content: [{
787
+ type: "text",
788
+ text: `Project created successfully:\nID: ${project.id}\n${formatProject(project)}`
789
+ }],
790
+ isError: false,
791
+ };
792
+ }
793
+ if (name === "todoist_update_project") {
794
+ if (!isUpdateProjectArgs(args)) {
795
+ throw new Error("Invalid arguments for todoist_update_project");
796
+ }
797
+ const updateData = {};
798
+ if (args.name)
799
+ updateData.name = args.name;
800
+ if (args.color)
801
+ updateData.color = args.color;
802
+ if (args.isFavorite !== undefined)
803
+ updateData.isFavorite = args.isFavorite;
804
+ if (args.viewStyle)
805
+ updateData.viewStyle = args.viewStyle;
806
+ const updatedProject = await todoistClient.updateProject(args.projectId, updateData);
807
+ return {
808
+ content: [{
809
+ type: "text",
810
+ text: `Project updated successfully:\nID: ${updatedProject.id}\n${formatProject(updatedProject)}`
811
+ }],
812
+ isError: false,
813
+ };
814
+ }
815
+ if (name === "todoist_delete_project") {
816
+ if (!isProjectIdArgs(args)) {
817
+ throw new Error("Invalid arguments for todoist_delete_project");
818
+ }
819
+ await todoistClient.deleteProject(args.projectId);
820
+ return {
821
+ content: [{
822
+ type: "text",
823
+ text: `Project ${args.projectId} deleted successfully`
824
+ }],
825
+ isError: false,
826
+ };
827
+ }
828
+ // Section operations
829
+ if (name === "todoist_get_sections") {
830
+ if (!isSectionArgs(args)) {
831
+ throw new Error("Invalid arguments for todoist_get_sections");
832
+ }
833
+ const params = {};
834
+ if (args.projectId)
835
+ params.projectId = args.projectId;
836
+ const sections = await todoistClient.getSections(Object.keys(params).length > 0 ? params : {});
837
+ // Handle simple array response
838
+ const sectionResults = Array.isArray(sections) ? sections : [];
839
+ const sectionList = sectionResults.map((section) => `- ${section.name} (ID: ${section.id}, Project: ${section.projectId})`).join('\n');
840
+ return {
841
+ content: [{
842
+ type: "text",
843
+ text: `Sections:\n${sectionList || 'No sections found'}`
844
+ }],
845
+ isError: false,
846
+ };
847
+ }
848
+ if (name === "todoist_create_section") {
849
+ if (!isCreateSectionArgs(args)) {
850
+ throw new Error("Invalid arguments for todoist_create_section");
851
+ }
852
+ const sectionData = {
853
+ name: args.name,
854
+ projectId: args.projectId
855
+ };
856
+ if (args.order !== undefined)
857
+ sectionData.order = args.order;
858
+ const section = await todoistClient.addSection(sectionData);
859
+ return {
860
+ content: [{
861
+ type: "text",
862
+ text: `Section created successfully:\nID: ${section.id}\nName: ${section.name}\nProject: ${section.projectId}`
863
+ }],
864
+ isError: false,
865
+ };
866
+ }
867
+ if (name === "todoist_update_section") {
868
+ if (!isUpdateSectionArgs(args)) {
869
+ throw new Error("Invalid arguments for todoist_update_section");
870
+ }
871
+ const updatedSection = await todoistClient.updateSection(args.sectionId, { name: args.name });
872
+ return {
873
+ content: [{
874
+ type: "text",
875
+ text: `Section updated successfully:\nID: ${updatedSection.id}\nName: ${updatedSection.name}`
876
+ }],
877
+ isError: false,
878
+ };
879
+ }
880
+ if (name === "todoist_delete_section") {
881
+ if (!isSectionIdArgs(args)) {
882
+ throw new Error("Invalid arguments for todoist_delete_section");
883
+ }
884
+ await todoistClient.deleteSection(args.sectionId);
885
+ return {
886
+ content: [{
887
+ type: "text",
888
+ text: `Section ${args.sectionId} deleted successfully`
889
+ }],
890
+ isError: false,
891
+ };
892
+ }
893
+ return {
894
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
895
+ isError: true,
896
+ };
897
+ }
898
+ catch (error) {
899
+ return {
900
+ content: [
901
+ {
902
+ type: "text",
903
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
904
+ },
905
+ ],
906
+ isError: true,
907
+ };
908
+ }
909
+ });
910
+ async function runServer() {
911
+ const transport = new StdioServerTransport();
912
+ await server.connect(transport);
913
+ console.error("Enhanced Todoist MCP Server running on stdio");
914
+ }
915
+ runServer().catch((error) => {
916
+ console.error("Fatal error running server:", error);
917
+ process.exit(1);
918
+ });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@kydycode/todoist-mcp-server-ext",
3
+ "version": "0.1.0",
4
+ "description": "Extended MCP server for Todoist API integration with enhanced features and improved compatibility",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "bin": {
9
+ "todoist-mcp-server-ext": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc && shx chmod +x dist/*.js",
16
+ "prepare": "npm run build",
17
+ "watch": "tsc --watch"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/kydycode/todoist-mcp-server-ext.git"
22
+ },
23
+ "keywords": [
24
+ "mcp",
25
+ "todoist",
26
+ "claude",
27
+ "ai",
28
+ "task-management",
29
+ "extended",
30
+ "enhanced",
31
+ "kydycode"
32
+ ],
33
+ "author": "kydycode (extended version) - Original by abhiz123",
34
+ "license": "MIT",
35
+ "bugs": {
36
+ "url": "https://github.com/kydycode/todoist-mcp-server-ext/issues"
37
+ },
38
+ "homepage": "https://github.com/kydycode/todoist-mcp-server-ext#readme",
39
+ "dependencies": {
40
+ "@doist/todoist-api-typescript": "^3.0.3",
41
+ "@modelcontextprotocol/sdk": "0.5.0",
42
+ "zod": "^3.22.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.10.1",
46
+ "shx": "^0.3.4",
47
+ "typescript": "^5.7.2"
48
+ }
49
+ }