# Directory Structure ``` ├── .gitignore ├── LICENSE ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Dependencies node_modules/ package-lock.json # Build output dist/ # Environment variables .env # IDE and OS files .DS_Store .vscode/ .idea/ # Logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Todoist MCP Server [](https://smithery.ai/server/@abhiz123/todoist-mcp-server) 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. <a href="https://glama.ai/mcp/servers/fhaif4fv1w"> <img width="380" height="200" src="https://glama.ai/mcp/servers/fhaif4fv1w/badge" alt="Todoist Server MCP server" /> </a> ## Features * **Natural Language Task Management**: Create, update, complete, and delete tasks using everyday language * **Smart Task Search**: Find tasks using partial name matches * **Flexible Filtering**: Filter tasks by due date, priority, and other attributes * **Rich Task Details**: Support for descriptions, due dates, and priority levels * **Intuitive Error Handling**: Clear feedback for better user experience ## Installation ### Installing via Smithery To install Todoist MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@abhiz123/todoist-mcp-server): ```bash npx -y @smithery/cli install @abhiz123/todoist-mcp-server --client claude ``` ### Manual Installation ```bash npm install -g @abhiz123/todoist-mcp-server ``` ## Tools ### todoist_create_task Create new tasks with various attributes: * Required: content (task title) * Optional: description, due date, priority level (1-4) * Example: "Create task 'Team Meeting' with description 'Weekly sync' due tomorrow" ### todoist_get_tasks Retrieve and filter tasks: * Filter by due date, priority, or project * Natural language date filtering * Optional result limit * Example: "Show high priority tasks due this week" ### todoist_update_task Update existing tasks using natural language search: * Find tasks by partial name match * Update any task attribute (content, description, due date, priority) * Example: "Update meeting task to be due next Monday" ### todoist_complete_task Mark tasks as complete using natural language search: * Find tasks by partial name match * Confirm completion status * Example: "Mark the documentation task as complete" ### todoist_delete_task Remove tasks using natural language search: * Find and delete tasks by name * Confirmation messages * Example: "Delete the PR review task" ## Setup ### Getting a Todoist API Token 1. Log in to your Todoist account 2. Navigate to Settings → Integrations 3. Find your API token under "Developer" ### Usage with Claude Desktop Add to your `claude_desktop_config.json`: ```json { "mcpServers": { "todoist": { "command": "npx", "args": ["-y", "@abhiz123/todoist-mcp-server"], "env": { "TODOIST_API_TOKEN": "your_api_token_here" } } } } ``` ## Example Usage ### Creating Tasks ``` "Create task 'Team Meeting'" "Add task 'Review PR' due tomorrow at 2pm" "Create high priority task 'Fix bug' with description 'Critical performance issue'" ``` ### Getting Tasks ``` "Show all my tasks" "List tasks due today" "Get high priority tasks" "Show tasks due this week" ``` ### Updating Tasks ``` "Update documentation task to be due next week" "Change priority of bug fix task to urgent" "Add description to team meeting task" ``` ### Completing Tasks ``` "Mark the PR review task as complete" "Complete the documentation task" ``` ### Deleting Tasks ``` "Delete the PR review task" "Remove meeting prep task" ``` ## Development ### Building from source ```bash # Clone the repository git clone https://github.com/abhiz123/todoist-mcp-server.git # Navigate to directory cd todoist-mcp-server # Install dependencies npm install # Build the project npm run build ``` ## Contributing Contributions are welcome! Feel free to submit a Pull Request. ## License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## Issues and Support 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 { "compilerOptions": { "target": "ES2020", "module": "ES2022", "moduleResolution": "bundler", "outDir": "./dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "declaration": true }, "include": ["src/index.ts"], "exclude": ["node_modules", "dist"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "@abhiz123/todoist-mcp-server", "version": "0.1.0", "description": "MCP server for Todoist API integration", "main": "dist/index.js", "types": "dist/index.d.ts", "type": "module", "bin": { "todoist-mcp-server": "dist/index.js" }, "files": [ "dist" ], "scripts": { "build": "tsc && shx chmod +x dist/*.js", "prepare": "npm run build", "watch": "tsc --watch" }, "repository": { "type": "git", "url": "git+https://github.com/abhiz123/todoist-mcp-server.git" }, "keywords": [ "mcp", "todoist", "claude", "ai", "task-management" ], "author": "abhiz123", "license": "MIT", "bugs": { "url": "https://github.com/abhiz123/todoist-mcp-server/issues" }, "homepage": "https://github.com/abhiz123/todoist-mcp-server#readme", "dependencies": { "@doist/todoist-api-typescript": "^3.0.3", "@modelcontextprotocol/sdk": "0.5.0" }, "devDependencies": { "@types/node": "^22.10.1", "shx": "^0.3.4", "typescript": "^5.7.2" } } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; import { TodoistApi } from "@doist/todoist-api-typescript"; // Define tools const CREATE_TASK_TOOL: Tool = { name: "todoist_create_task", description: "Create a new task in Todoist with optional description, due date, and priority", inputSchema: { type: "object", properties: { content: { type: "string", description: "The content/title of the task" }, description: { type: "string", description: "Detailed description of the task (optional)" }, due_string: { type: "string", description: "Natural language due date like 'tomorrow', 'next Monday', 'Jan 23' (optional)" }, priority: { type: "number", description: "Task priority from 1 (normal) to 4 (urgent) (optional)", enum: [1, 2, 3, 4] } }, required: ["content"] } }; const GET_TASKS_TOOL: Tool = { name: "todoist_get_tasks", description: "Get a list of tasks from Todoist with various filters", inputSchema: { type: "object", properties: { project_id: { type: "string", description: "Filter tasks by project ID (optional)" }, filter: { type: "string", description: "Natural language filter like 'today', 'tomorrow', 'next week', 'priority 1', 'overdue' (optional)" }, priority: { type: "number", description: "Filter by priority level (1-4) (optional)", enum: [1, 2, 3, 4] }, limit: { type: "number", description: "Maximum number of tasks to return (optional)", default: 10 } } } }; const UPDATE_TASK_TOOL: Tool = { name: "todoist_update_task", description: "Update an existing task in Todoist by searching for it by name and then updating it", inputSchema: { type: "object", properties: { task_name: { type: "string", description: "Name/content of the task to search for and update" }, content: { type: "string", description: "New content/title for the task (optional)" }, description: { type: "string", description: "New description for the task (optional)" }, due_string: { type: "string", description: "New due date in natural language like 'tomorrow', 'next Monday' (optional)" }, priority: { type: "number", description: "New priority level from 1 (normal) to 4 (urgent) (optional)", enum: [1, 2, 3, 4] } }, required: ["task_name"] } }; const DELETE_TASK_TOOL: Tool = { name: "todoist_delete_task", description: "Delete a task from Todoist by searching for it by name", inputSchema: { type: "object", properties: { task_name: { type: "string", description: "Name/content of the task to search for and delete" } }, required: ["task_name"] } }; const COMPLETE_TASK_TOOL: Tool = { name: "todoist_complete_task", description: "Mark a task as complete by searching for it by name", inputSchema: { type: "object", properties: { task_name: { type: "string", description: "Name/content of the task to search for and complete" } }, required: ["task_name"] } }; // Server implementation const server = new Server( { name: "todoist-mcp-server", version: "0.1.0", }, { capabilities: { tools: {}, }, }, ); // Check for API token const TODOIST_API_TOKEN = process.env.TODOIST_API_TOKEN!; if (!TODOIST_API_TOKEN) { console.error("Error: TODOIST_API_TOKEN environment variable is required"); process.exit(1); } // Initialize Todoist client const todoistClient = new TodoistApi(TODOIST_API_TOKEN); // Type guards for arguments function isCreateTaskArgs(args: unknown): args is { content: string; description?: string; due_string?: string; priority?: number; } { return ( typeof args === "object" && args !== null && "content" in args && typeof (args as { content: string }).content === "string" ); } function isGetTasksArgs(args: unknown): args is { project_id?: string; filter?: string; priority?: number; limit?: number; } { return ( typeof args === "object" && args !== null ); } function isUpdateTaskArgs(args: unknown): args is { task_name: string; content?: string; description?: string; due_string?: string; priority?: number; } { return ( typeof args === "object" && args !== null && "task_name" in args && typeof (args as { task_name: string }).task_name === "string" ); } function isDeleteTaskArgs(args: unknown): args is { task_name: string; } { return ( typeof args === "object" && args !== null && "task_name" in args && typeof (args as { task_name: string }).task_name === "string" ); } function isCompleteTaskArgs(args: unknown): args is { task_name: string; } { return ( typeof args === "object" && args !== null && "task_name" in args && typeof (args as { task_name: string }).task_name === "string" ); } // Tool handlers server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [CREATE_TASK_TOOL, GET_TASKS_TOOL, UPDATE_TASK_TOOL, DELETE_TASK_TOOL, COMPLETE_TASK_TOOL], })); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) { throw new Error("No arguments provided"); } if (name === "todoist_create_task") { if (!isCreateTaskArgs(args)) { throw new Error("Invalid arguments for todoist_create_task"); } const task = await todoistClient.addTask({ content: args.content, description: args.description, dueString: args.due_string, priority: args.priority }); return { content: [{ type: "text", text: `Task created:\nTitle: ${task.content}${task.description ? `\nDescription: ${task.description}` : ''}${task.due ? `\nDue: ${task.due.string}` : ''}${task.priority ? `\nPriority: ${task.priority}` : ''}` }], isError: false, }; } if (name === "todoist_get_tasks") { if (!isGetTasksArgs(args)) { throw new Error("Invalid arguments for todoist_get_tasks"); } // Only pass filter if at least one filtering parameter is provided const apiParams: any = {}; if (args.project_id) { apiParams.projectId = args.project_id; } if (args.filter) { apiParams.filter = args.filter; } // If no filters provided, default to showing all tasks const tasks = await todoistClient.getTasks(Object.keys(apiParams).length > 0 ? apiParams : undefined); // Apply additional filters let filteredTasks = tasks; if (args.priority) { filteredTasks = filteredTasks.filter(task => task.priority === args.priority); } // Apply limit if (args.limit && args.limit > 0) { filteredTasks = filteredTasks.slice(0, args.limit); } const taskList = filteredTasks.map(task => `- ${task.content}${task.description ? `\n Description: ${task.description}` : ''}${task.due ? `\n Due: ${task.due.string}` : ''}${task.priority ? `\n Priority: ${task.priority}` : ''}` ).join('\n\n'); return { content: [{ type: "text", text: filteredTasks.length > 0 ? taskList : "No tasks found matching the criteria" }], isError: false, }; } if (name === "todoist_update_task") { if (!isUpdateTaskArgs(args)) { throw new Error("Invalid arguments for todoist_update_task"); } // First, search for the task const tasks = await todoistClient.getTasks(); const matchingTask = tasks.find(task => task.content.toLowerCase().includes(args.task_name.toLowerCase()) ); if (!matchingTask) { return { content: [{ type: "text", text: `Could not find a task matching "${args.task_name}"` }], isError: true, }; } // Build update data const updateData: any = {}; if (args.content) updateData.content = args.content; if (args.description) updateData.description = args.description; if (args.due_string) updateData.dueString = args.due_string; if (args.priority) updateData.priority = args.priority; const updatedTask = await todoistClient.updateTask(matchingTask.id, updateData); return { content: [{ type: "text", 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}` : ''}` }], isError: false, }; } if (name === "todoist_delete_task") { if (!isDeleteTaskArgs(args)) { throw new Error("Invalid arguments for todoist_delete_task"); } // First, search for the task const tasks = await todoistClient.getTasks(); const matchingTask = tasks.find(task => task.content.toLowerCase().includes(args.task_name.toLowerCase()) ); if (!matchingTask) { return { content: [{ type: "text", text: `Could not find a task matching "${args.task_name}"` }], isError: true, }; } // Delete the task await todoistClient.deleteTask(matchingTask.id); return { content: [{ type: "text", text: `Successfully deleted task: "${matchingTask.content}"` }], isError: false, }; } if (name === "todoist_complete_task") { if (!isCompleteTaskArgs(args)) { throw new Error("Invalid arguments for todoist_complete_task"); } // First, search for the task const tasks = await todoistClient.getTasks(); const matchingTask = tasks.find(task => task.content.toLowerCase().includes(args.task_name.toLowerCase()) ); if (!matchingTask) { return { content: [{ type: "text", text: `Could not find a task matching "${args.task_name}"` }], isError: true, }; } // Complete the task await todoistClient.closeTask(matchingTask.id); return { content: [{ type: "text", text: `Successfully completed task: "${matchingTask.content}"` }], isError: false, }; } return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true, }; } catch (error) { return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Todoist MCP Server running on stdio"); } runServer().catch((error) => { console.error("Fatal error running server:", error); process.exit(1); }); ```