todoist-mcp 1.2.4 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,24 +32,43 @@ You'll need a Todoist API token to use this MCP server.
32
32
  2. Navigate to Settings → Integrations
33
33
  3. Find your API token under "Developer"
34
34
 
35
- ### Usage with Claude Desktop
35
+ ### Usage
36
36
 
37
- Add to your `claude_desktop_config.json`:
37
+ Add to `mcpServers` in your platform config:
38
38
 
39
39
  ```json
40
- {
41
- "mcpServers": {
42
- "todoist": {
43
- "command": "npx",
44
- "args": ["-y", "todoist-mcp"],
45
- "env": {
46
- "API_KEY": "your_todoist_api_token_here"
47
- }
48
- }
49
- }
40
+ "todoist": {
41
+ "command": "npx",
42
+ "args": ["-y", "todoist-mcp"],
43
+ "env": { "API_KEY": "your_todoist_api_token" }
50
44
  }
51
45
  ```
52
46
 
47
+ | Platform | Config |
48
+ |----------|--------|
49
+ | Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%\Claude\` (Windows) |
50
+ | Claude Code | `.mcp.json` — `claude mcp add --transport stdio --scope project --env API_KEY=token todoist -- npx -y todoist-mcp` |
51
+ | Cursor | `~/.cursor/mcp.json` or `.cursor/mcp.json` |
52
+ | Codex | `~/.codex/config.toml` or `.codex/config.toml` — see example below |
53
+ | Gemini CLI | `~/.gemini/settings.json` — `gemini mcp add -e API_KEY=token todoist npx -y todoist-mcp` |
54
+
55
+ **Codex** — CLI:
56
+
57
+ ```bash
58
+ codex mcp add todoist --env API_KEY=your_token -- npx -y todoist-mcp
59
+ ```
60
+
61
+ Or add to `config.toml`:
62
+
63
+ ```toml
64
+ [mcp_servers.todoist]
65
+ command = "npx"
66
+ args = ["-y", "todoist-mcp"]
67
+
68
+ [mcp_servers.todoist.env]
69
+ API_KEY = "your_todoist_api_token"
70
+ ```
71
+
53
72
  ## Available Tools
54
73
 
55
74
  ### Tasks
@@ -48,21 +48,29 @@ const create_fields = {
48
48
  };
49
49
  createApiHandler({
50
50
  name: 'get_tasks_list',
51
- description: 'Get tasks list from Todoist',
51
+ description: 'Get tasks list from Todoist. For advanced filtering use get_tasks_by_filter tool',
52
52
  schemaShape: {
53
53
  project_id: z.string().optional().describe('Filter by project'),
54
54
  section_id: z.string().optional().describe('Filter by section'),
55
55
  label: z.string().optional().describe('Filter by label'),
56
- filter: z
57
- .string()
58
- .optional()
59
- .describe('Natural language english filter like "search: keyword", "today", "date before: +4 hours", "date after: May 5", "no date", "no time", "overdue", "7 days & @waiting", "created before: -365 days", "assigned to: person", "added by: me", "#Project & !assigned", "subtask", "!subtask", "P1 | P2", "today & @email", "@work | @office", "(today | overdue) & #Work", "all & 7 days", "!assigned", "Today & !#Work"'),
60
56
  ids: z.string().optional().describe('Comma-separated list of task IDs'),
61
57
  limit: z.number().optional().default(50),
62
58
  },
63
59
  method: 'GET',
64
60
  path: '/tasks',
65
61
  });
62
+ createApiHandler({
63
+ name: 'get_tasks_by_filter',
64
+ description: 'Get tasks from Todoist using filter language. Use for queries like "today", "overdue", "P1", date-based filters, label/project filters in query syntax',
65
+ schemaShape: {
66
+ query: z
67
+ .string()
68
+ .describe('Todoist filter query. Examples: "today", "overdue", "today | overdue", "no date", "no time", "P1 | P2", "7 days & @waiting", "created before: -365 days", "assigned to: person", "added by: me", "#Project & !assigned", "subtask", "!subtask", "today & @email", "@work | @office", "(today | overdue) & #Work", "all & 7 days", "!assigned", "search: keyword"'),
69
+ limit: z.number().optional().default(50),
70
+ },
71
+ method: 'GET',
72
+ path: '/tasks/filter',
73
+ });
66
74
  createBatchApiHandler({
67
75
  name: 'create_tasks',
68
76
  description: 'Create new tasks in Todoist',
@@ -191,18 +199,20 @@ createSyncApiHandler({
191
199
  return { valid: true };
192
200
  },
193
201
  });
194
- createHandler('get_completed_tasks', 'Get completed tasks from Todoist with filtering options', {
195
- project_id: z.string().optional().describe('Filter by specific project ID'),
196
- section_id: z.string().optional().describe('Filter by specific section ID'),
197
- parent_id: z.string().optional().describe('Filter by specific parent task ID'),
202
+ createHandler('get_completed_tasks', 'Get completed tasks from Todoist with filtering options. Date range limited to 3 months max.', {
198
203
  since: z
199
204
  .string()
200
- .optional()
201
- .describe('Return tasks completed since this date (YYYY-MM-DD format)'),
205
+ .describe('Start of completion date range (ISO 8601 date-time, e.g. "2025-01-01T00:00:00Z")'),
202
206
  until: z
207
+ .string()
208
+ .describe('End of completion date range (ISO 8601 date-time, e.g. "2025-03-31T23:59:59Z")'),
209
+ project_id: z.string().optional().describe('Filter by specific project ID'),
210
+ section_id: z.string().optional().describe('Filter by specific section ID'),
211
+ parent_id: z.string().optional().describe('Filter by specific parent task ID'),
212
+ cursor: z
203
213
  .string()
204
214
  .optional()
205
- .describe('Return tasks completed until this date (YYYY-MM-DD format)'),
215
+ .describe('Cursor for pagination (from next_cursor in previous response)'),
206
216
  limit: z
207
217
  .number()
208
218
  .int()
@@ -211,8 +221,6 @@ createHandler('get_completed_tasks', 'Get completed tasks from Todoist with filt
211
221
  .optional()
212
222
  .default(50)
213
223
  .describe('Number of tasks to return (max 200)'),
214
- offset: z.number().int().min(0).optional().describe('Offset for pagination'),
215
- annotation_type: z.string().optional().describe('Filter by annotation type'),
216
224
  }, async (args) => {
217
225
  const params = {};
218
226
  Object.entries(args).forEach(([key, value]) => {
@@ -220,11 +228,5 @@ createHandler('get_completed_tasks', 'Get completed tasks from Todoist with filt
220
228
  params[key] = typeof value === 'number' ? value.toString() : value;
221
229
  }
222
230
  });
223
- const response = await todoistApi.getCompletedTasks(params);
224
- return {
225
- completed_tasks: response.items || [],
226
- projects: response.projects || {},
227
- sections: response.sections || {},
228
- total: response.items?.length || 0,
229
- };
231
+ return todoistApi.getCompletedTasks(params);
230
232
  });
@@ -31,7 +31,7 @@ export declare class TodoistClient {
31
31
  */
32
32
  sync(commands: Array<SyncCommand>): Promise<any>;
33
33
  /**
34
- * Get completed tasks using Sync API
34
+ * Get completed tasks via REST API
35
35
  * @param params - Query parameters for filtering completed tasks
36
36
  * @returns API response data with completed tasks
37
37
  */
@@ -1,8 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { v4 as uuidv4 } from 'uuid';
3
3
  import { log } from './helpers.js';
4
- const API_BASE_URL = 'https://api.todoist.com/rest/v2';
5
- const API_SYNC_BASE_URL = 'https://api.todoist.com/sync/v9';
4
+ const API_BASE_URL = 'https://api.todoist.com/api/v1';
6
5
  export class TodoistClient {
7
6
  apiToken;
8
7
  constructor(apiToken) {
@@ -53,7 +52,12 @@ export class TodoistClient {
53
52
  method: 'GET',
54
53
  headers: this.getHeaders(),
55
54
  });
56
- return this.handleResponse(response);
55
+ const data = await this.handleResponse(response);
56
+ // API v1 wraps list responses in { results: [...] }
57
+ if (data && typeof data === 'object' && Array.isArray(data.results)) {
58
+ return data.results;
59
+ }
60
+ return data;
57
61
  }
58
62
  /**
59
63
  * Make a POST request to Todoist API
@@ -91,7 +95,7 @@ export class TodoistClient {
91
95
  * @returns API response data
92
96
  */
93
97
  async sync(commands) {
94
- const url = `${API_SYNC_BASE_URL}/sync`;
98
+ const url = `${API_BASE_URL}/sync`;
95
99
  log(`Making SYNC request to: ${url} with commands:`, JSON.stringify(commands, null, 2));
96
100
  const response = await fetch(url, {
97
101
  method: 'POST',
@@ -101,28 +105,11 @@ export class TodoistClient {
101
105
  return this.handleResponse(response);
102
106
  }
103
107
  /**
104
- * Get completed tasks using Sync API
108
+ * Get completed tasks via REST API
105
109
  * @param params - Query parameters for filtering completed tasks
106
110
  * @returns API response data with completed tasks
107
111
  */
108
112
  async getCompletedTasks(params = {}) {
109
- const url = `${API_SYNC_BASE_URL}/completed/get_all`;
110
- log(`Making completed tasks request to: ${url} with params:`, JSON.stringify(params, null, 2));
111
- const formData = new URLSearchParams();
112
- for (const [key, value] of Object.entries(params)) {
113
- if (value) {
114
- formData.append(key, value);
115
- }
116
- }
117
- const response = await fetch(url, {
118
- method: 'POST',
119
- headers: {
120
- Authorization: `Bearer ${this.apiToken}`,
121
- 'Content-Type': 'application/x-www-form-urlencoded',
122
- 'X-Request-Id': uuidv4(),
123
- },
124
- body: formData.toString(),
125
- });
126
- return this.handleResponse(response);
113
+ return this.get('/tasks/completed/by_completion_date', params);
127
114
  }
128
115
  }
@@ -1 +1 @@
1
- export declare const version = "1.2.4";
1
+ export declare const version = "1.3.1";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated file, do not edit
2
- export const version = '1.2.4';
2
+ export const version = '1.3.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "todoist-mcp",
3
- "version": "1.2.4",
3
+ "version": "1.3.1",
4
4
  "description": "Todoist MCP Server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -61,4 +61,4 @@
61
61
  "vite": "^5.0.0",
62
62
  "vitest": "^1.0.0"
63
63
  }
64
- }
64
+ }