#
tokens: 5997/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── LICENSE
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependencies
 2 | node_modules/
 3 | package-lock.json
 4 | 
 5 | # Build output
 6 | dist/
 7 | 
 8 | # Environment variables
 9 | .env
10 | 
11 | # IDE and OS files
12 | .DS_Store
13 | .vscode/
14 | .idea/
15 | 
16 | # Logs
17 | *.log
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Todoist MCP Server
  2 | [![smithery badge](https://smithery.ai/badge/@abhiz123/todoist-mcp-server)](https://smithery.ai/server/@abhiz123/todoist-mcp-server)
  3 | 
  4 | An MCP (Model Context Protocol) server implementation that integrates Claude with Todoist, enabling natural language task management. This server allows Claude to interact with your Todoist tasks using everyday language.
  5 | 
  6 | <a href="https://glama.ai/mcp/servers/fhaif4fv1w">
  7 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/fhaif4fv1w/badge" alt="Todoist Server MCP server" />
  8 | </a>
  9 | 
 10 | ## Features
 11 | 
 12 | * **Natural Language Task Management**: Create, update, complete, and delete tasks using everyday language
 13 | * **Smart Task Search**: Find tasks using partial name matches
 14 | * **Flexible Filtering**: Filter tasks by due date, priority, and other attributes
 15 | * **Rich Task Details**: Support for descriptions, due dates, and priority levels
 16 | * **Intuitive Error Handling**: Clear feedback for better user experience
 17 | 
 18 | ## Installation
 19 | 
 20 | ### Installing via Smithery
 21 | 
 22 | To install Todoist MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@abhiz123/todoist-mcp-server):
 23 | 
 24 | ```bash
 25 | npx -y @smithery/cli install @abhiz123/todoist-mcp-server --client claude
 26 | ```
 27 | 
 28 | ### Manual Installation
 29 | ```bash
 30 | npm install -g @abhiz123/todoist-mcp-server
 31 | ```
 32 | 
 33 | ## Tools
 34 | 
 35 | ### todoist_create_task
 36 | Create new tasks with various attributes:
 37 | * Required: content (task title)
 38 | * Optional: description, due date, priority level (1-4)
 39 | * Example: "Create task 'Team Meeting' with description 'Weekly sync' due tomorrow"
 40 | 
 41 | ### todoist_get_tasks
 42 | Retrieve and filter tasks:
 43 | * Filter by due date, priority, or project
 44 | * Natural language date filtering
 45 | * Optional result limit
 46 | * Example: "Show high priority tasks due this week"
 47 | 
 48 | ### todoist_update_task
 49 | Update existing tasks using natural language search:
 50 | * Find tasks by partial name match
 51 | * Update any task attribute (content, description, due date, priority)
 52 | * Example: "Update meeting task to be due next Monday"
 53 | 
 54 | ### todoist_complete_task
 55 | Mark tasks as complete using natural language search:
 56 | * Find tasks by partial name match
 57 | * Confirm completion status
 58 | * Example: "Mark the documentation task as complete"
 59 | 
 60 | ### todoist_delete_task
 61 | Remove tasks using natural language search:
 62 | * Find and delete tasks by name
 63 | * Confirmation messages
 64 | * Example: "Delete the PR review task"
 65 | 
 66 | ## Setup
 67 | 
 68 | ### Getting a Todoist API Token
 69 | 1. Log in to your Todoist account
 70 | 2. Navigate to Settings → Integrations
 71 | 3. Find your API token under "Developer"
 72 | 
 73 | ### Usage with Claude Desktop
 74 | 
 75 | Add to your `claude_desktop_config.json`:
 76 | 
 77 | ```json
 78 | {
 79 |   "mcpServers": {
 80 |     "todoist": {
 81 |       "command": "npx",
 82 |       "args": ["-y", "@abhiz123/todoist-mcp-server"],
 83 |       "env": {
 84 |         "TODOIST_API_TOKEN": "your_api_token_here"
 85 |       }
 86 |     }
 87 |   }
 88 | }
 89 | ```
 90 | 
 91 | ## Example Usage
 92 | 
 93 | ### Creating Tasks
 94 | ```
 95 | "Create task 'Team Meeting'"
 96 | "Add task 'Review PR' due tomorrow at 2pm"
 97 | "Create high priority task 'Fix bug' with description 'Critical performance issue'"
 98 | ```
 99 | 
100 | ### Getting Tasks
101 | ```
102 | "Show all my tasks"
103 | "List tasks due today"
104 | "Get high priority tasks"
105 | "Show tasks due this week"
106 | ```
107 | 
108 | ### Updating Tasks
109 | ```
110 | "Update documentation task to be due next week"
111 | "Change priority of bug fix task to urgent"
112 | "Add description to team meeting task"
113 | ```
114 | 
115 | ### Completing Tasks
116 | ```
117 | "Mark the PR review task as complete"
118 | "Complete the documentation task"
119 | ```
120 | 
121 | ### Deleting Tasks
122 | ```
123 | "Delete the PR review task"
124 | "Remove meeting prep task"
125 | ```
126 | 
127 | ## Development
128 | 
129 | ### Building from source
130 | ```bash
131 | # Clone the repository
132 | git clone https://github.com/abhiz123/todoist-mcp-server.git
133 | 
134 | # Navigate to directory
135 | cd todoist-mcp-server
136 | 
137 | # Install dependencies
138 | npm install
139 | 
140 | # Build the project
141 | npm run build
142 | ```
143 | 
144 | ## Contributing
145 | Contributions are welcome! Feel free to submit a Pull Request.
146 | 
147 | ## License
148 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
149 | 
150 | ## Issues and Support
151 | If you encounter any issues or need support, please file an issue on the [GitHub repository](https://github.com/abhiz123/todoist-mcp-server/issues).
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2020",
 4 |     "module": "ES2022",
 5 |     "moduleResolution": "bundler",
 6 |     "outDir": "./dist",
 7 |     "strict": true,
 8 |     "esModuleInterop": true,
 9 |     "skipLibCheck": true,
10 |     "declaration": true
11 |   },
12 |   "include": ["src/index.ts"],
13 |   "exclude": ["node_modules", "dist"]
14 | }
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@abhiz123/todoist-mcp-server",
 3 |   "version": "0.1.0",
 4 |   "description": "MCP server for Todoist API integration",
 5 |   "main": "dist/index.js",
 6 |   "types": "dist/index.d.ts",
 7 |   "type": "module",
 8 |   "bin": {
 9 |     "todoist-mcp-server": "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/abhiz123/todoist-mcp-server.git"
22 |   },
23 |   "keywords": [
24 |     "mcp",
25 |     "todoist",
26 |     "claude",
27 |     "ai",
28 |     "task-management"
29 |   ],
30 |   "author": "abhiz123",
31 |   "license": "MIT",
32 |   "bugs": {
33 |     "url": "https://github.com/abhiz123/todoist-mcp-server/issues"
34 |   },
35 |   "homepage": "https://github.com/abhiz123/todoist-mcp-server#readme",
36 |   "dependencies": {
37 |     "@doist/todoist-api-typescript": "^3.0.3",
38 |     "@modelcontextprotocol/sdk": "0.5.0"
39 |   },
40 |   "devDependencies": {
41 |     "@types/node": "^22.10.1",
42 |     "shx": "^0.3.4",
43 |     "typescript": "^5.7.2"
44 |   }
45 | }
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |   CallToolRequestSchema,
  7 |   ListToolsRequestSchema,
  8 |   Tool,
  9 | } from "@modelcontextprotocol/sdk/types.js";
 10 | import { TodoistApi } from "@doist/todoist-api-typescript";
 11 | 
 12 | // Define tools
 13 | const CREATE_TASK_TOOL: Tool = {
 14 |   name: "todoist_create_task",
 15 |   description: "Create a new task in Todoist with optional description, due date, and priority",
 16 |   inputSchema: {
 17 |     type: "object",
 18 |     properties: {
 19 |       content: {
 20 |         type: "string",
 21 |         description: "The content/title of the task"
 22 |       },
 23 |       description: {
 24 |         type: "string",
 25 |         description: "Detailed description of the task (optional)"
 26 |       },
 27 |       due_string: {
 28 |         type: "string",
 29 |         description: "Natural language due date like 'tomorrow', 'next Monday', 'Jan 23' (optional)"
 30 |       },
 31 |       priority: {
 32 |         type: "number",
 33 |         description: "Task priority from 1 (normal) to 4 (urgent) (optional)",
 34 |         enum: [1, 2, 3, 4]
 35 |       }
 36 |     },
 37 |     required: ["content"]
 38 |   }
 39 | };
 40 | 
 41 | const GET_TASKS_TOOL: Tool = {
 42 |   name: "todoist_get_tasks",
 43 |   description: "Get a list of tasks from Todoist with various filters",
 44 |   inputSchema: {
 45 |     type: "object",
 46 |     properties: {
 47 |       project_id: {
 48 |         type: "string",
 49 |         description: "Filter tasks by project ID (optional)"
 50 |       },
 51 |       filter: {
 52 |         type: "string",
 53 |         description: "Natural language filter like 'today', 'tomorrow', 'next week', 'priority 1', 'overdue' (optional)"
 54 |       },
 55 |       priority: {
 56 |         type: "number",
 57 |         description: "Filter by priority level (1-4) (optional)",
 58 |         enum: [1, 2, 3, 4]
 59 |       },
 60 |       limit: {
 61 |         type: "number",
 62 |         description: "Maximum number of tasks to return (optional)",
 63 |         default: 10
 64 |       }
 65 |     }
 66 |   }
 67 | };
 68 | 
 69 | const UPDATE_TASK_TOOL: Tool = {
 70 |   name: "todoist_update_task",
 71 |   description: "Update an existing task in Todoist by searching for it by name and then updating it",
 72 |   inputSchema: {
 73 |     type: "object",
 74 |     properties: {
 75 |       task_name: {
 76 |         type: "string",
 77 |         description: "Name/content of the task to search for and update"
 78 |       },
 79 |       content: {
 80 |         type: "string",
 81 |         description: "New content/title for the task (optional)"
 82 |       },
 83 |       description: {
 84 |         type: "string",
 85 |         description: "New description for the task (optional)"
 86 |       },
 87 |       due_string: {
 88 |         type: "string",
 89 |         description: "New due date in natural language like 'tomorrow', 'next Monday' (optional)"
 90 |       },
 91 |       priority: {
 92 |         type: "number",
 93 |         description: "New priority level from 1 (normal) to 4 (urgent) (optional)",
 94 |         enum: [1, 2, 3, 4]
 95 |       }
 96 |     },
 97 |     required: ["task_name"]
 98 |   }
 99 | };
100 | 
101 | const DELETE_TASK_TOOL: Tool = {
102 |   name: "todoist_delete_task",
103 |   description: "Delete a task from Todoist by searching for it by name",
104 |   inputSchema: {
105 |     type: "object",
106 |     properties: {
107 |       task_name: {
108 |         type: "string",
109 |         description: "Name/content of the task to search for and delete"
110 |       }
111 |     },
112 |     required: ["task_name"]
113 |   }
114 | };
115 | 
116 | const COMPLETE_TASK_TOOL: Tool = {
117 |   name: "todoist_complete_task",
118 |   description: "Mark a task as complete by searching for it by name",
119 |   inputSchema: {
120 |     type: "object",
121 |     properties: {
122 |       task_name: {
123 |         type: "string",
124 |         description: "Name/content of the task to search for and complete"
125 |       }
126 |     },
127 |     required: ["task_name"]
128 |   }
129 | };
130 | 
131 | // Server implementation
132 | const server = new Server(
133 |   {
134 |     name: "todoist-mcp-server",
135 |     version: "0.1.0",
136 |   },
137 |   {
138 |     capabilities: {
139 |       tools: {},
140 |     },
141 |   },
142 | );
143 | 
144 | // Check for API token
145 | const TODOIST_API_TOKEN = process.env.TODOIST_API_TOKEN!;
146 | if (!TODOIST_API_TOKEN) {
147 |   console.error("Error: TODOIST_API_TOKEN environment variable is required");
148 |   process.exit(1);
149 | }
150 | 
151 | // Initialize Todoist client
152 | const todoistClient = new TodoistApi(TODOIST_API_TOKEN);
153 | 
154 | // Type guards for arguments
155 | function isCreateTaskArgs(args: unknown): args is { 
156 |   content: string;
157 |   description?: string;
158 |   due_string?: string;
159 |   priority?: number;
160 | } {
161 |   return (
162 |     typeof args === "object" &&
163 |     args !== null &&
164 |     "content" in args &&
165 |     typeof (args as { content: string }).content === "string"
166 |   );
167 | }
168 | 
169 | function isGetTasksArgs(args: unknown): args is { 
170 |   project_id?: string;
171 |   filter?: string;
172 |   priority?: number;
173 |   limit?: number;
174 | } {
175 |   return (
176 |     typeof args === "object" &&
177 |     args !== null
178 |   );
179 | }
180 | 
181 | function isUpdateTaskArgs(args: unknown): args is {
182 |   task_name: string;
183 |   content?: string;
184 |   description?: string;
185 |   due_string?: string;
186 |   priority?: number;
187 | } {
188 |   return (
189 |     typeof args === "object" &&
190 |     args !== null &&
191 |     "task_name" in args &&
192 |     typeof (args as { task_name: string }).task_name === "string"
193 |   );
194 | }
195 | 
196 | function isDeleteTaskArgs(args: unknown): args is {
197 |   task_name: string;
198 | } {
199 |   return (
200 |     typeof args === "object" &&
201 |     args !== null &&
202 |     "task_name" in args &&
203 |     typeof (args as { task_name: string }).task_name === "string"
204 |   );
205 | }
206 | 
207 | function isCompleteTaskArgs(args: unknown): args is {
208 |   task_name: string;
209 | } {
210 |   return (
211 |     typeof args === "object" &&
212 |     args !== null &&
213 |     "task_name" in args &&
214 |     typeof (args as { task_name: string }).task_name === "string"
215 |   );
216 | }
217 | 
218 | // Tool handlers
219 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
220 |   tools: [CREATE_TASK_TOOL, GET_TASKS_TOOL, UPDATE_TASK_TOOL, DELETE_TASK_TOOL, COMPLETE_TASK_TOOL],
221 | }));
222 | 
223 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
224 |   try {
225 |     const { name, arguments: args } = request.params;
226 | 
227 |     if (!args) {
228 |       throw new Error("No arguments provided");
229 |     }
230 | 
231 |     if (name === "todoist_create_task") {
232 |       if (!isCreateTaskArgs(args)) {
233 |         throw new Error("Invalid arguments for todoist_create_task");
234 |       }
235 |       const task = await todoistClient.addTask({
236 |         content: args.content,
237 |         description: args.description,
238 |         dueString: args.due_string,
239 |         priority: args.priority
240 |       });
241 |       return {
242 |         content: [{ 
243 |           type: "text", 
244 |           text: `Task created:\nTitle: ${task.content}${task.description ? `\nDescription: ${task.description}` : ''}${task.due ? `\nDue: ${task.due.string}` : ''}${task.priority ? `\nPriority: ${task.priority}` : ''}` 
245 |         }],
246 |         isError: false,
247 |       };
248 |     }
249 | 
250 |     if (name === "todoist_get_tasks") {
251 |       if (!isGetTasksArgs(args)) {
252 |         throw new Error("Invalid arguments for todoist_get_tasks");
253 |       }
254 |       
255 |       // Only pass filter if at least one filtering parameter is provided
256 |       const apiParams: any = {};
257 |       if (args.project_id) {
258 |         apiParams.projectId = args.project_id;
259 |       }
260 |       if (args.filter) {
261 |         apiParams.filter = args.filter;
262 |       }
263 |       // If no filters provided, default to showing all tasks
264 |       const tasks = await todoistClient.getTasks(Object.keys(apiParams).length > 0 ? apiParams : undefined);
265 | 
266 |       // Apply additional filters
267 |       let filteredTasks = tasks;
268 |       if (args.priority) {
269 |         filteredTasks = filteredTasks.filter(task => task.priority === args.priority);
270 |       }
271 |       
272 |       // Apply limit
273 |       if (args.limit && args.limit > 0) {
274 |         filteredTasks = filteredTasks.slice(0, args.limit);
275 |       }
276 |       
277 |       const taskList = filteredTasks.map(task => 
278 |         `- ${task.content}${task.description ? `\n  Description: ${task.description}` : ''}${task.due ? `\n  Due: ${task.due.string}` : ''}${task.priority ? `\n  Priority: ${task.priority}` : ''}`
279 |       ).join('\n\n');
280 |       
281 |       return {
282 |         content: [{ 
283 |           type: "text", 
284 |           text: filteredTasks.length > 0 ? taskList : "No tasks found matching the criteria" 
285 |         }],
286 |         isError: false,
287 |       };
288 |     }
289 | 
290 |     if (name === "todoist_update_task") {
291 |       if (!isUpdateTaskArgs(args)) {
292 |         throw new Error("Invalid arguments for todoist_update_task");
293 |       }
294 | 
295 |       // First, search for the task
296 |       const tasks = await todoistClient.getTasks();
297 |       const matchingTask = tasks.find(task => 
298 |         task.content.toLowerCase().includes(args.task_name.toLowerCase())
299 |       );
300 | 
301 |       if (!matchingTask) {
302 |         return {
303 |           content: [{ 
304 |             type: "text", 
305 |             text: `Could not find a task matching "${args.task_name}"` 
306 |           }],
307 |           isError: true,
308 |         };
309 |       }
310 | 
311 |       // Build update data
312 |       const updateData: any = {};
313 |       if (args.content) updateData.content = args.content;
314 |       if (args.description) updateData.description = args.description;
315 |       if (args.due_string) updateData.dueString = args.due_string;
316 |       if (args.priority) updateData.priority = args.priority;
317 | 
318 |       const updatedTask = await todoistClient.updateTask(matchingTask.id, updateData);
319 |       
320 |       return {
321 |         content: [{ 
322 |           type: "text", 
323 |           text: `Task "${matchingTask.content}" updated:\nNew Title: ${updatedTask.content}${updatedTask.description ? `\nNew Description: ${updatedTask.description}` : ''}${updatedTask.due ? `\nNew Due Date: ${updatedTask.due.string}` : ''}${updatedTask.priority ? `\nNew Priority: ${updatedTask.priority}` : ''}` 
324 |         }],
325 |         isError: false,
326 |       };
327 |     }
328 | 
329 |     if (name === "todoist_delete_task") {
330 |       if (!isDeleteTaskArgs(args)) {
331 |         throw new Error("Invalid arguments for todoist_delete_task");
332 |       }
333 | 
334 |       // First, search for the task
335 |       const tasks = await todoistClient.getTasks();
336 |       const matchingTask = tasks.find(task => 
337 |         task.content.toLowerCase().includes(args.task_name.toLowerCase())
338 |       );
339 | 
340 |       if (!matchingTask) {
341 |         return {
342 |           content: [{ 
343 |             type: "text", 
344 |             text: `Could not find a task matching "${args.task_name}"` 
345 |           }],
346 |           isError: true,
347 |         };
348 |       }
349 | 
350 |       // Delete the task
351 |       await todoistClient.deleteTask(matchingTask.id);
352 |       
353 |       return {
354 |         content: [{ 
355 |           type: "text", 
356 |           text: `Successfully deleted task: "${matchingTask.content}"` 
357 |         }],
358 |         isError: false,
359 |       };
360 |     }
361 | 
362 |     if (name === "todoist_complete_task") {
363 |       if (!isCompleteTaskArgs(args)) {
364 |         throw new Error("Invalid arguments for todoist_complete_task");
365 |       }
366 | 
367 |       // First, search for the task
368 |       const tasks = await todoistClient.getTasks();
369 |       const matchingTask = tasks.find(task => 
370 |         task.content.toLowerCase().includes(args.task_name.toLowerCase())
371 |       );
372 | 
373 |       if (!matchingTask) {
374 |         return {
375 |           content: [{ 
376 |             type: "text", 
377 |             text: `Could not find a task matching "${args.task_name}"` 
378 |           }],
379 |           isError: true,
380 |         };
381 |       }
382 | 
383 |       // Complete the task
384 |       await todoistClient.closeTask(matchingTask.id);
385 |       
386 |       return {
387 |         content: [{ 
388 |           type: "text", 
389 |           text: `Successfully completed task: "${matchingTask.content}"` 
390 |         }],
391 |         isError: false,
392 |       };
393 |     }
394 | 
395 |     return {
396 |       content: [{ type: "text", text: `Unknown tool: ${name}` }],
397 |       isError: true,
398 |     };
399 |   } catch (error) {
400 |     return {
401 |       content: [
402 |         {
403 |           type: "text",
404 |           text: `Error: ${error instanceof Error ? error.message : String(error)}`,
405 |         },
406 |       ],
407 |       isError: true,
408 |     };
409 |   }
410 | });
411 | 
412 | async function runServer() {
413 |   const transport = new StdioServerTransport();
414 |   await server.connect(transport);
415 |   console.error("Todoist MCP Server running on stdio");
416 | }
417 | 
418 | runServer().catch((error) => {
419 |   console.error("Fatal error running server:", error);
420 |   process.exit(1);
421 | });
```