@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 +21 -0
- package/README.md +261 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +918 -0
- package/package.json +49 -0
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
|
+
[](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)
|
package/dist/index.d.ts
ADDED
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
|
+
}
|