ticktick-mcp 0.2.0 → 0.2.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.
Files changed (91) hide show
  1. package/dist/index.js +47 -1582
  2. package/dist/index.js.map +1 -1
  3. package/dist/tools/authExchangeCode.d.ts +9 -0
  4. package/dist/tools/authExchangeCode.d.ts.map +1 -0
  5. package/dist/tools/authExchangeCode.js +49 -0
  6. package/dist/tools/authExchangeCode.js.map +1 -0
  7. package/dist/tools/authGetAuthorizationUrl.d.ts +10 -0
  8. package/dist/tools/authGetAuthorizationUrl.d.ts.map +1 -0
  9. package/dist/tools/authGetAuthorizationUrl.js +53 -0
  10. package/dist/tools/authGetAuthorizationUrl.js.map +1 -0
  11. package/dist/tools/authLogout.d.ts +9 -0
  12. package/dist/tools/authLogout.d.ts.map +1 -0
  13. package/dist/tools/authLogout.js +43 -0
  14. package/dist/tools/authLogout.js.map +1 -0
  15. package/dist/tools/authRefreshToken.d.ts +9 -0
  16. package/dist/tools/authRefreshToken.d.ts.map +1 -0
  17. package/dist/tools/authRefreshToken.js +60 -0
  18. package/dist/tools/authRefreshToken.js.map +1 -0
  19. package/dist/tools/authStatus.d.ts +9 -0
  20. package/dist/tools/authStatus.d.ts.map +1 -0
  21. package/dist/tools/authStatus.js +63 -0
  22. package/dist/tools/authStatus.js.map +1 -0
  23. package/dist/tools/batchCreateTasks.d.ts +9 -0
  24. package/dist/tools/batchCreateTasks.d.ts.map +1 -0
  25. package/dist/tools/batchCreateTasks.js +75 -0
  26. package/dist/tools/batchCreateTasks.js.map +1 -0
  27. package/dist/tools/completeTask.d.ts +9 -0
  28. package/dist/tools/completeTask.d.ts.map +1 -0
  29. package/dist/tools/completeTask.js +47 -0
  30. package/dist/tools/completeTask.js.map +1 -0
  31. package/dist/tools/createProject.d.ts +9 -0
  32. package/dist/tools/createProject.d.ts.map +1 -0
  33. package/dist/tools/createProject.js +64 -0
  34. package/dist/tools/createProject.js.map +1 -0
  35. package/dist/tools/createTask.d.ts +9 -0
  36. package/dist/tools/createTask.d.ts.map +1 -0
  37. package/dist/tools/createTask.js +131 -0
  38. package/dist/tools/createTask.js.map +1 -0
  39. package/dist/tools/deleteProject.d.ts +9 -0
  40. package/dist/tools/deleteProject.d.ts.map +1 -0
  41. package/dist/tools/deleteProject.js +46 -0
  42. package/dist/tools/deleteProject.js.map +1 -0
  43. package/dist/tools/deleteTask.d.ts +9 -0
  44. package/dist/tools/deleteTask.d.ts.map +1 -0
  45. package/dist/tools/deleteTask.js +47 -0
  46. package/dist/tools/deleteTask.js.map +1 -0
  47. package/dist/tools/getHighPriorityTasks.d.ts +9 -0
  48. package/dist/tools/getHighPriorityTasks.d.ts.map +1 -0
  49. package/dist/tools/getHighPriorityTasks.js +114 -0
  50. package/dist/tools/getHighPriorityTasks.js.map +1 -0
  51. package/dist/tools/getProject.d.ts +9 -0
  52. package/dist/tools/getProject.d.ts.map +1 -0
  53. package/dist/tools/getProject.js +62 -0
  54. package/dist/tools/getProject.js.map +1 -0
  55. package/dist/tools/getProjectById.d.ts +9 -0
  56. package/dist/tools/getProjectById.d.ts.map +1 -0
  57. package/dist/tools/getProjectById.js +52 -0
  58. package/dist/tools/getProjectById.js.map +1 -0
  59. package/dist/tools/getTask.d.ts +9 -0
  60. package/dist/tools/getTask.d.ts.map +1 -0
  61. package/dist/tools/getTask.js +65 -0
  62. package/dist/tools/getTask.js.map +1 -0
  63. package/dist/tools/getTasksDueSoon.d.ts +9 -0
  64. package/dist/tools/getTasksDueSoon.d.ts.map +1 -0
  65. package/dist/tools/getTasksDueSoon.js +118 -0
  66. package/dist/tools/getTasksDueSoon.js.map +1 -0
  67. package/dist/tools/getUser.d.ts +9 -0
  68. package/dist/tools/getUser.d.ts.map +1 -0
  69. package/dist/tools/getUser.js +47 -0
  70. package/dist/tools/getUser.js.map +1 -0
  71. package/dist/tools/listProjects.d.ts +9 -0
  72. package/dist/tools/listProjects.d.ts.map +1 -0
  73. package/dist/tools/listProjects.js +78 -0
  74. package/dist/tools/listProjects.js.map +1 -0
  75. package/dist/tools/listTasksInProject.d.ts +10 -0
  76. package/dist/tools/listTasksInProject.d.ts.map +1 -0
  77. package/dist/tools/listTasksInProject.js +177 -0
  78. package/dist/tools/listTasksInProject.js.map +1 -0
  79. package/dist/tools/searchTasks.d.ts +9 -0
  80. package/dist/tools/searchTasks.d.ts.map +1 -0
  81. package/dist/tools/searchTasks.js +134 -0
  82. package/dist/tools/searchTasks.js.map +1 -0
  83. package/dist/tools/updateProject.d.ts +9 -0
  84. package/dist/tools/updateProject.d.ts.map +1 -0
  85. package/dist/tools/updateProject.js +66 -0
  86. package/dist/tools/updateProject.js.map +1 -0
  87. package/dist/tools/updateTask.d.ts +9 -0
  88. package/dist/tools/updateTask.d.ts.map +1 -0
  89. package/dist/tools/updateTask.js +132 -0
  90. package/dist/tools/updateTask.js.map +1 -0
  91. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,9 +7,31 @@
7
7
  */
8
8
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
- import { z } from "zod";
11
10
  import { createOAuthFromEnv } from "./oauth.js";
12
11
  import { TickTickClient } from "./sdk/client.js";
12
+ // Import all tool registration functions
13
+ import { registerAuthGetAuthorizationUrlTool } from "./tools/authGetAuthorizationUrl.js";
14
+ import { registerAuthExchangeCodeTool } from "./tools/authExchangeCode.js";
15
+ import { registerAuthStatusTool } from "./tools/authStatus.js";
16
+ import { registerAuthRefreshTokenTool } from "./tools/authRefreshToken.js";
17
+ import { registerAuthLogoutTool } from "./tools/authLogout.js";
18
+ import { registerGetUserTool } from "./tools/getUser.js";
19
+ import { registerListProjectsTool } from "./tools/listProjects.js";
20
+ import { registerGetProjectTool } from "./tools/getProject.js";
21
+ import { registerGetProjectByIdTool } from "./tools/getProjectById.js";
22
+ import { registerCreateProjectTool } from "./tools/createProject.js";
23
+ import { registerUpdateProjectTool } from "./tools/updateProject.js";
24
+ import { registerDeleteProjectTool } from "./tools/deleteProject.js";
25
+ import { registerListTasksInProjectTool } from "./tools/listTasksInProject.js";
26
+ import { registerCreateTaskTool } from "./tools/createTask.js";
27
+ import { registerUpdateTaskTool } from "./tools/updateTask.js";
28
+ import { registerCompleteTaskTool } from "./tools/completeTask.js";
29
+ import { registerDeleteTaskTool } from "./tools/deleteTask.js";
30
+ import { registerGetTaskTool } from "./tools/getTask.js";
31
+ import { registerBatchCreateTasksTool } from "./tools/batchCreateTasks.js";
32
+ import { registerGetTasksDueSoonTool } from "./tools/getTasksDueSoon.js";
33
+ import { registerSearchTasksTool } from "./tools/searchTasks.js";
34
+ import { registerGetHighPriorityTasksTool } from "./tools/getHighPriorityTasks.js";
13
35
  // =============================================================================
14
36
  // Server Setup
15
37
  // =============================================================================
@@ -38,1591 +60,34 @@ async function getClient() {
38
60
  return new TickTickClient({ accessToken, region });
39
61
  }
40
62
  // =============================================================================
41
- // OAuth Authentication Tools
42
- // =============================================================================
43
- /**
44
- * Tool: Get OAuth authorization URL
45
- *
46
- * Returns the URL that users should visit to authorize the application.
47
- * This initiates the OAuth 2.0 authorization code flow.
48
- */
49
- server.tool("auth_get_authorization_url", "Get the OAuth authorization URL for TickTick. Returns a URL that the user should visit in their browser to authorize the application.", {}, async () => {
50
- try {
51
- const oauth = getOAuthHelper();
52
- const { url, state } = oauth.getAuthorizationUrl();
53
- return {
54
- content: [
55
- {
56
- type: "text",
57
- text: JSON.stringify({
58
- success: true,
59
- authorizationUrl: url,
60
- state: state,
61
- instructions: [
62
- "1. Open the authorization URL in your browser",
63
- "2. Log in to TickTick and authorize the application",
64
- "3. After authorization, you will be redirected to a URL containing a 'code' parameter",
65
- "4. Copy the 'code' value from the URL",
66
- "5. Use the auth_exchange_code tool with the code to complete authentication",
67
- ],
68
- }, null, 2),
69
- },
70
- ],
71
- };
72
- }
73
- catch (error) {
74
- const errorMessage = error instanceof Error ? error.message : String(error);
75
- return {
76
- content: [
77
- {
78
- type: "text",
79
- text: JSON.stringify({
80
- success: false,
81
- error: errorMessage,
82
- hint: "Make sure TICKTICK_CLIENT_ID and TICKTICK_CLIENT_SECRET environment variables are set",
83
- }, null, 2),
84
- },
85
- ],
86
- isError: true,
87
- };
88
- }
89
- });
90
- /**
91
- * Tool: Exchange authorization code for tokens
92
- *
93
- * Exchanges an authorization code (from the OAuth callback) for access and refresh tokens.
94
- */
95
- server.tool("auth_exchange_code", "Exchange an OAuth authorization code for access tokens. Use this after the user has authorized the application and received a code.", {
96
- code: z.string().describe("The authorization code from the OAuth callback URL"),
97
- }, async ({ code }) => {
98
- try {
99
- const oauth = getOAuthHelper();
100
- const tokens = await oauth.exchangeCode(code);
101
- await oauth.storeToken(tokens);
102
- return {
103
- content: [
104
- {
105
- type: "text",
106
- text: JSON.stringify({
107
- success: true,
108
- message: "Authentication successful! Tokens have been stored.",
109
- expiresIn: tokens.expires_in,
110
- tokenType: tokens.token_type,
111
- }, null, 2),
112
- },
113
- ],
114
- };
115
- }
116
- catch (error) {
117
- const errorMessage = error instanceof Error ? error.message : String(error);
118
- return {
119
- content: [
120
- {
121
- type: "text",
122
- text: JSON.stringify({
123
- success: false,
124
- error: errorMessage,
125
- }, null, 2),
126
- },
127
- ],
128
- isError: true,
129
- };
130
- }
131
- });
132
- /**
133
- * Tool: Check authentication status
134
- *
135
- * Returns information about the current authentication state.
136
- */
137
- server.tool("auth_status", "Check the current authentication status. Shows whether the user is authenticated and when the token expires.", {}, async () => {
138
- try {
139
- const oauth = getOAuthHelper();
140
- const status = await oauth.getAuthStatus();
141
- if (!status.isAuthenticated) {
142
- return {
143
- content: [
144
- {
145
- type: "text",
146
- text: JSON.stringify({
147
- success: true,
148
- isAuthenticated: false,
149
- message: "Not authenticated. Use auth_get_authorization_url to start the OAuth flow.",
150
- }, null, 2),
151
- },
152
- ],
153
- };
154
- }
155
- return {
156
- content: [
157
- {
158
- type: "text",
159
- text: JSON.stringify({
160
- success: true,
161
- isAuthenticated: true,
162
- isExpired: status.isExpired,
163
- expiresAt: status.expiresAt,
164
- expiresIn: status.expiresIn,
165
- message: status.isExpired
166
- ? "Token is expired. It will be automatically refreshed on next API call."
167
- : `Token is valid for ${status.expiresIn} more seconds.`,
168
- }, null, 2),
169
- },
170
- ],
171
- };
172
- }
173
- catch (error) {
174
- const errorMessage = error instanceof Error ? error.message : String(error);
175
- return {
176
- content: [
177
- {
178
- type: "text",
179
- text: JSON.stringify({
180
- success: false,
181
- error: errorMessage,
182
- }, null, 2),
183
- },
184
- ],
185
- isError: true,
186
- };
187
- }
188
- });
189
- /**
190
- * Tool: Refresh access token
191
- *
192
- * Manually refresh the access token using the stored refresh token.
193
- */
194
- server.tool("auth_refresh_token", "Manually refresh the OAuth access token. Usually not needed as tokens are automatically refreshed when expired.", {}, async () => {
195
- try {
196
- const oauth = getOAuthHelper();
197
- const storedToken = await oauth.loadToken();
198
- if (!storedToken) {
199
- return {
200
- content: [
201
- {
202
- type: "text",
203
- text: JSON.stringify({
204
- success: false,
205
- error: "Not authenticated. No stored token found.",
206
- }, null, 2),
207
- },
208
- ],
209
- isError: true,
210
- };
211
- }
212
- const newTokens = await oauth.refreshToken(storedToken.refreshToken);
213
- await oauth.storeToken(newTokens);
214
- return {
215
- content: [
216
- {
217
- type: "text",
218
- text: JSON.stringify({
219
- success: true,
220
- message: "Token refreshed successfully!",
221
- expiresIn: newTokens.expires_in,
222
- }, null, 2),
223
- },
224
- ],
225
- };
226
- }
227
- catch (error) {
228
- const errorMessage = error instanceof Error ? error.message : String(error);
229
- return {
230
- content: [
231
- {
232
- type: "text",
233
- text: JSON.stringify({
234
- success: false,
235
- error: errorMessage,
236
- }, null, 2),
237
- },
238
- ],
239
- isError: true,
240
- };
241
- }
242
- });
243
- /**
244
- * Tool: Logout / Clear tokens
245
- *
246
- * Remove stored authentication tokens.
247
- */
248
- server.tool("auth_logout", "Remove stored authentication tokens. This will log out the user from the TickTick integration.", {}, async () => {
249
- try {
250
- const oauth = getOAuthHelper();
251
- await oauth.clearToken();
252
- return {
253
- content: [
254
- {
255
- type: "text",
256
- text: JSON.stringify({
257
- success: true,
258
- message: "Logged out successfully. Stored tokens have been cleared.",
259
- }, null, 2),
260
- },
261
- ],
262
- };
263
- }
264
- catch (error) {
265
- const errorMessage = error instanceof Error ? error.message : String(error);
266
- return {
267
- content: [
268
- {
269
- type: "text",
270
- text: JSON.stringify({
271
- success: false,
272
- error: errorMessage,
273
- }, null, 2),
274
- },
275
- ],
276
- isError: true,
277
- };
278
- }
279
- });
63
+ // Register All Tools
280
64
  // =============================================================================
65
+ // OAuth Authentication Tools
66
+ registerAuthGetAuthorizationUrlTool(server, getOAuthHelper);
67
+ registerAuthExchangeCodeTool(server, getOAuthHelper);
68
+ registerAuthStatusTool(server, getOAuthHelper);
69
+ registerAuthRefreshTokenTool(server, getOAuthHelper);
70
+ registerAuthLogoutTool(server, getOAuthHelper);
281
71
  // User Tools
282
- // =============================================================================
283
- /**
284
- * Tool: Get current user
285
- *
286
- * Returns information about the authenticated TickTick user.
287
- */
288
- server.tool("get_user", "Get the current authenticated user's information from TickTick.", {}, async () => {
289
- try {
290
- const client = await getClient();
291
- const user = await client.getUser();
292
- return {
293
- content: [
294
- {
295
- type: "text",
296
- text: JSON.stringify({
297
- success: true,
298
- user: {
299
- id: user.id,
300
- username: user.username,
301
- name: user.name,
302
- },
303
- }, null, 2),
304
- },
305
- ],
306
- };
307
- }
308
- catch (error) {
309
- const errorMessage = error instanceof Error ? error.message : String(error);
310
- return {
311
- content: [
312
- {
313
- type: "text",
314
- text: JSON.stringify({
315
- success: false,
316
- error: errorMessage,
317
- }, null, 2),
318
- },
319
- ],
320
- isError: true,
321
- };
322
- }
323
- });
324
- // =============================================================================
72
+ registerGetUserTool(server, getClient);
325
73
  // Project Tools
326
- // =============================================================================
327
- /**
328
- * Tool: List all projects
329
- *
330
- * Returns all projects for the authenticated user.
331
- */
332
- server.tool("list_projects", "List all projects in the user's TickTick account with optional filtering. By default, only returns active (non-archived) projects.", {
333
- includeArchived: z.boolean().optional().describe("Include archived/closed projects (default: false)"),
334
- kind: z.enum(['TASK', 'NOTE']).optional().describe("Filter by project type"),
335
- viewMode: z.enum(['list', 'kanban', 'timeline']).optional().describe("Filter by view mode"),
336
- search: z.string().optional().describe("Text search in project name (case-insensitive)"),
337
- limit: z.number().optional().describe("Maximum number of projects to return"),
338
- }, async ({ includeArchived, kind, viewMode, search, limit }) => {
339
- try {
340
- const client = await getClient();
341
- let projects = await client.listProjects();
342
- // Filter out archived projects by default
343
- if (!includeArchived) {
344
- projects = projects.filter(p => !p.closed);
345
- }
346
- // Filter by kind
347
- if (kind) {
348
- projects = projects.filter(p => p.kind === kind);
349
- }
350
- // Filter by viewMode
351
- if (viewMode) {
352
- projects = projects.filter(p => p.viewMode === viewMode);
353
- }
354
- // Filter by search text
355
- if (search) {
356
- const searchLower = search.toLowerCase();
357
- projects = projects.filter(p => p.name.toLowerCase().includes(searchLower));
358
- }
359
- // Apply limit
360
- if (limit && limit > 0) {
361
- projects = projects.slice(0, limit);
362
- }
363
- return {
364
- content: [
365
- {
366
- type: "text",
367
- text: JSON.stringify({
368
- success: true,
369
- count: projects.length,
370
- projects: projects.map((p) => ({
371
- id: p.id,
372
- name: p.name,
373
- color: p.color,
374
- viewMode: p.viewMode,
375
- closed: p.closed,
376
- })),
377
- }, null, 2),
378
- },
379
- ],
380
- };
381
- }
382
- catch (error) {
383
- const errorMessage = error instanceof Error ? error.message : String(error);
384
- return {
385
- content: [
386
- {
387
- type: "text",
388
- text: JSON.stringify({
389
- success: false,
390
- error: errorMessage,
391
- }, null, 2),
392
- },
393
- ],
394
- isError: true,
395
- };
396
- }
397
- });
398
- /**
399
- * Tool: Get project with tasks
400
- *
401
- * Returns a specific project and all its tasks.
402
- */
403
- server.tool("get_project", "Get a specific project and all its tasks.", {
404
- projectId: z.string().describe("The ID of the project to retrieve"),
405
- }, async ({ projectId }) => {
406
- try {
407
- const client = await getClient();
408
- const data = await client.getProjectWithTasks(projectId);
409
- return {
410
- content: [
411
- {
412
- type: "text",
413
- text: JSON.stringify({
414
- success: true,
415
- project: {
416
- id: data.project.id,
417
- name: data.project.name,
418
- color: data.project.color,
419
- viewMode: data.project.viewMode,
420
- closed: data.project.closed,
421
- },
422
- taskCount: data.tasks.length,
423
- tasks: data.tasks.map((t) => ({
424
- id: t.id,
425
- title: t.title,
426
- content: t.content,
427
- priority: t.priority,
428
- status: t.status,
429
- dueDate: t.dueDate,
430
- tags: t.tags,
431
- })),
432
- }, null, 2),
433
- },
434
- ],
435
- };
436
- }
437
- catch (error) {
438
- const errorMessage = error instanceof Error ? error.message : String(error);
439
- return {
440
- content: [
441
- {
442
- type: "text",
443
- text: JSON.stringify({
444
- success: false,
445
- error: errorMessage,
446
- }, null, 2),
447
- },
448
- ],
449
- isError: true,
450
- };
451
- }
452
- });
453
- /**
454
- * Tool: Get project by ID (metadata only)
455
- *
456
- * Returns a specific project's metadata without its tasks.
457
- */
458
- server.tool("get_project_by_id", "Get a specific project's metadata by ID (without tasks). Use this when you only need project info, not its tasks.", {
459
- projectId: z.string().describe("The ID of the project to retrieve"),
460
- }, async ({ projectId }) => {
461
- try {
462
- const client = await getClient();
463
- const project = await client.getProject(projectId);
464
- return {
465
- content: [
466
- {
467
- type: "text",
468
- text: JSON.stringify({
469
- success: true,
470
- project: {
471
- id: project.id,
472
- name: project.name,
473
- color: project.color,
474
- viewMode: project.viewMode,
475
- closed: project.closed,
476
- },
477
- }, null, 2),
478
- },
479
- ],
480
- };
481
- }
482
- catch (error) {
483
- const errorMessage = error instanceof Error ? error.message : String(error);
484
- return {
485
- content: [
486
- {
487
- type: "text",
488
- text: JSON.stringify({
489
- success: false,
490
- error: errorMessage,
491
- }, null, 2),
492
- },
493
- ],
494
- isError: true,
495
- };
496
- }
497
- });
498
- /**
499
- * Tool: Create project
500
- *
501
- * Creates a new project.
502
- */
503
- server.tool("create_project", "Create a new project in TickTick.", {
504
- name: z.string().describe("The name of the project"),
505
- color: z
506
- .string()
507
- .optional()
508
- .describe('Hex color code (e.g., "#ff6b6b")'),
509
- viewMode: z
510
- .enum(["list", "kanban", "timeline"])
511
- .optional()
512
- .describe("View mode for the project"),
513
- }, async ({ name, color, viewMode }) => {
514
- try {
515
- const client = await getClient();
516
- const project = await client.createProject({
517
- name,
518
- color,
519
- viewMode: viewMode,
520
- });
521
- return {
522
- content: [
523
- {
524
- type: "text",
525
- text: JSON.stringify({
526
- success: true,
527
- message: `Project "${project.name}" created successfully!`,
528
- project: {
529
- id: project.id,
530
- name: project.name,
531
- color: project.color,
532
- viewMode: project.viewMode,
533
- },
534
- }, null, 2),
535
- },
536
- ],
537
- };
538
- }
539
- catch (error) {
540
- const errorMessage = error instanceof Error ? error.message : String(error);
541
- return {
542
- content: [
543
- {
544
- type: "text",
545
- text: JSON.stringify({
546
- success: false,
547
- error: errorMessage,
548
- }, null, 2),
549
- },
550
- ],
551
- isError: true,
552
- };
553
- }
554
- });
555
- /**
556
- * Tool: Update project
557
- *
558
- * Updates an existing project.
559
- */
560
- server.tool("update_project", "Update an existing project in TickTick.", {
561
- projectId: z.string().describe("The ID of the project to update"),
562
- name: z.string().optional().describe("New name for the project"),
563
- color: z
564
- .string()
565
- .optional()
566
- .describe('New hex color code (e.g., "#ff6b6b")'),
567
- viewMode: z
568
- .enum(["list", "kanban", "timeline"])
569
- .optional()
570
- .describe("New view mode for the project"),
571
- }, async ({ projectId, name, color, viewMode }) => {
572
- try {
573
- const client = await getClient();
574
- const project = await client.updateProject(projectId, {
575
- name,
576
- color,
577
- viewMode: viewMode,
578
- });
579
- return {
580
- content: [
581
- {
582
- type: "text",
583
- text: JSON.stringify({
584
- success: true,
585
- message: `Project "${project.name}" updated successfully!`,
586
- project: {
587
- id: project.id,
588
- name: project.name,
589
- color: project.color,
590
- viewMode: project.viewMode,
591
- closed: project.closed,
592
- },
593
- }, null, 2),
594
- },
595
- ],
596
- };
597
- }
598
- catch (error) {
599
- const errorMessage = error instanceof Error ? error.message : String(error);
600
- return {
601
- content: [
602
- {
603
- type: "text",
604
- text: JSON.stringify({
605
- success: false,
606
- error: errorMessage,
607
- }, null, 2),
608
- },
609
- ],
610
- isError: true,
611
- };
612
- }
613
- });
614
- /**
615
- * Tool: Delete project
616
- *
617
- * Deletes a project.
618
- */
619
- server.tool("delete_project", "Delete a project from TickTick.", {
620
- projectId: z.string().describe("The ID of the project to delete"),
621
- }, async ({ projectId }) => {
622
- try {
623
- const client = await getClient();
624
- await client.deleteProject(projectId);
625
- return {
626
- content: [
627
- {
628
- type: "text",
629
- text: JSON.stringify({
630
- success: true,
631
- message: "Project deleted successfully!",
632
- }, null, 2),
633
- },
634
- ],
635
- };
636
- }
637
- catch (error) {
638
- const errorMessage = error instanceof Error ? error.message : String(error);
639
- return {
640
- content: [
641
- {
642
- type: "text",
643
- text: JSON.stringify({
644
- success: false,
645
- error: errorMessage,
646
- }, null, 2),
647
- },
648
- ],
649
- isError: true,
650
- };
651
- }
652
- });
653
- // =============================================================================
74
+ registerListProjectsTool(server, getClient);
75
+ registerGetProjectTool(server, getClient);
76
+ registerGetProjectByIdTool(server, getClient);
77
+ registerCreateProjectTool(server, getClient);
78
+ registerUpdateProjectTool(server, getClient);
79
+ registerDeleteProjectTool(server, getClient);
654
80
  // Task Tools
655
- // =============================================================================
656
- /**
657
- * Tool: List tasks in project
658
- *
659
- * Returns all tasks in a specific project (without project metadata).
660
- * This is a convenience tool that wraps getProjectWithTasks but only returns tasks.
661
- */
662
- server.tool("list_tasks_in_project", "List all tasks in a specific project with optional client-side filtering and sorting. Returns only the tasks array without project metadata.", {
663
- projectId: z.string().describe("The ID of the project to list tasks from"),
664
- status: z.enum(['active', 'completed', 'all']).optional().describe("Filter by task status (default: 'all')"),
665
- priority: z.union([z.number(), z.array(z.number())]).optional().describe("Filter by priority: single value (0,1,3,5) or array [0,1,3,5]"),
666
- dueBefore: z.string().optional().describe("ISO date - include only tasks due before this date"),
667
- dueAfter: z.string().optional().describe("ISO date - include only tasks due after this date"),
668
- startBefore: z.string().optional().describe("ISO date - include only tasks starting before this date"),
669
- startAfter: z.string().optional().describe("ISO date - include only tasks starting after this date"),
670
- hasSubtasks: z.boolean().optional().describe("Filter by presence of subtasks: true=with subtasks, false=without"),
671
- tags: z.array(z.string()).optional().describe("Filter by tags - include tasks with any of these tags"),
672
- search: z.string().optional().describe("Text search - filter tasks whose title or content contains this text (case-insensitive)"),
673
- sortBy: z.enum(['dueDate', 'priority', 'title', 'createdTime', 'modifiedTime', 'sortOrder']).optional().describe("Field to sort by"),
674
- sortDirection: z.enum(['asc', 'desc']).optional().describe("Sort direction (default: 'asc')"),
675
- limit: z.number().optional().describe("Maximum number of tasks to return"),
676
- }, async ({ projectId, status, priority, dueBefore, dueAfter, startBefore, startAfter, hasSubtasks, tags, search, sortBy, sortDirection, limit }) => {
677
- try {
678
- const client = await getClient();
679
- const data = await client.getProjectWithTasks(projectId);
680
- // Apply filters
681
- let filteredTasks = data.tasks;
682
- // Filter by status
683
- if (status && status !== 'all') {
684
- filteredTasks = filteredTasks.filter(t => {
685
- if (status === 'active')
686
- return t.status === 0;
687
- if (status === 'completed')
688
- return t.status === 2;
689
- return true;
690
- });
691
- }
692
- // Filter by priority
693
- if (priority !== undefined) {
694
- const priorities = Array.isArray(priority) ? priority : [priority];
695
- filteredTasks = filteredTasks.filter(t => priorities.includes(t.priority));
696
- }
697
- // Filter by due date
698
- if (dueBefore) {
699
- const beforeDate = new Date(dueBefore);
700
- filteredTasks = filteredTasks.filter(t => t.dueDate && new Date(t.dueDate) < beforeDate);
701
- }
702
- if (dueAfter) {
703
- const afterDate = new Date(dueAfter);
704
- filteredTasks = filteredTasks.filter(t => t.dueDate && new Date(t.dueDate) > afterDate);
705
- }
706
- // Filter by start date
707
- if (startBefore) {
708
- const beforeDate = new Date(startBefore);
709
- filteredTasks = filteredTasks.filter(t => t.startDate && new Date(t.startDate) < beforeDate);
710
- }
711
- if (startAfter) {
712
- const afterDate = new Date(startAfter);
713
- filteredTasks = filteredTasks.filter(t => t.startDate && new Date(t.startDate) > afterDate);
714
- }
715
- // Filter by subtasks
716
- if (hasSubtasks !== undefined) {
717
- filteredTasks = filteredTasks.filter(t => {
718
- const hasItems = t.items && t.items.length > 0;
719
- return hasSubtasks ? hasItems : !hasItems;
720
- });
721
- }
722
- // Filter by tags
723
- if (tags && tags.length > 0) {
724
- filteredTasks = filteredTasks.filter(t => {
725
- return t.tags && t.tags.some(tag => tags.includes(tag));
726
- });
727
- }
728
- // Filter by search text
729
- if (search) {
730
- const searchLower = search.toLowerCase();
731
- filteredTasks = filteredTasks.filter(t => {
732
- return t.title.toLowerCase().includes(searchLower) ||
733
- (t.content && t.content.toLowerCase().includes(searchLower));
734
- });
735
- }
736
- // Apply sorting
737
- if (sortBy) {
738
- const direction = sortDirection === 'desc' ? -1 : 1;
739
- filteredTasks.sort((a, b) => {
740
- let aVal;
741
- let bVal;
742
- switch (sortBy) {
743
- case 'dueDate':
744
- aVal = a.dueDate ? new Date(a.dueDate).getTime() : Infinity;
745
- bVal = b.dueDate ? new Date(b.dueDate).getTime() : Infinity;
746
- break;
747
- case 'priority':
748
- aVal = a.priority;
749
- bVal = b.priority;
750
- break;
751
- case 'title':
752
- aVal = a.title.toLowerCase();
753
- bVal = b.title.toLowerCase();
754
- break;
755
- case 'createdTime':
756
- aVal = new Date(a.createdTime).getTime();
757
- bVal = new Date(b.createdTime).getTime();
758
- break;
759
- case 'modifiedTime':
760
- aVal = new Date(a.modifiedTime).getTime();
761
- bVal = new Date(b.modifiedTime).getTime();
762
- break;
763
- case 'sortOrder':
764
- aVal = a.sortOrder;
765
- bVal = b.sortOrder;
766
- break;
767
- default:
768
- return 0;
769
- }
770
- if (aVal < bVal)
771
- return -1 * direction;
772
- if (aVal > bVal)
773
- return 1 * direction;
774
- return 0;
775
- });
776
- }
777
- // Apply limit
778
- if (limit && limit > 0) {
779
- filteredTasks = filteredTasks.slice(0, limit);
780
- }
781
- return {
782
- content: [
783
- {
784
- type: "text",
785
- text: JSON.stringify({
786
- success: true,
787
- total: data.tasks.length,
788
- count: filteredTasks.length,
789
- tasks: filteredTasks.map((t) => ({
790
- id: t.id,
791
- title: t.title,
792
- content: t.content,
793
- priority: t.priority,
794
- status: t.status,
795
- dueDate: t.dueDate,
796
- startDate: t.startDate,
797
- isAllDay: t.isAllDay,
798
- tags: t.tags,
799
- items: t.items?.map((item) => ({
800
- id: item.id,
801
- title: item.title,
802
- status: item.status,
803
- })),
804
- })),
805
- }, null, 2),
806
- },
807
- ],
808
- };
809
- }
810
- catch (error) {
811
- const errorMessage = error instanceof Error ? error.message : String(error);
812
- return {
813
- content: [
814
- {
815
- type: "text",
816
- text: JSON.stringify({
817
- success: false,
818
- error: errorMessage,
819
- }, null, 2),
820
- },
821
- ],
822
- isError: true,
823
- };
824
- }
825
- });
826
- /**
827
- * Tool: Create task
828
- *
829
- * Creates a new task.
830
- */
831
- server.tool("create_task", "Create a new task in TickTick.", {
832
- title: z.string().describe("The title of the task"),
833
- projectId: z
834
- .string()
835
- .optional()
836
- .describe("The project ID (defaults to inbox if not specified)"),
837
- content: z.string().optional().describe("Task description/notes"),
838
- dueDate: z
839
- .string()
840
- .optional()
841
- .describe("Due date in ISO 8601 format (e.g., 2024-01-15T17:00:00+0000)"),
842
- priority: z
843
- .number()
844
- .min(0)
845
- .max(5)
846
- .optional()
847
- .describe("Priority: 0=None, 1=Low, 3=Medium, 5=High"),
848
- tags: z.array(z.string()).optional().describe("Array of tag names"),
849
- isAllDay: z
850
- .boolean()
851
- .optional()
852
- .describe("Whether this is an all-day task (no specific time)"),
853
- startDate: z
854
- .string()
855
- .optional()
856
- .describe("Start date in ISO 8601 format (e.g., 2024-01-15T09:00:00+0000)"),
857
- timeZone: z
858
- .string()
859
- .optional()
860
- .describe("IANA timezone (e.g., 'America/New_York', 'Europe/London')"),
861
- reminders: z
862
- .array(z.string())
863
- .optional()
864
- .describe("Array of reminder strings in iCalendar TRIGGER format (e.g., 'TRIGGER:P0DT9H0M0S' for 9:00 AM, 'TRIGGER:-PT15M' for 15 minutes before)"),
865
- repeat: z
866
- .string()
867
- .optional()
868
- .describe("Recurrence rule in RRULE format (e.g., 'RRULE:FREQ=DAILY;INTERVAL=1' for daily, 'RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR' for Mon/Wed/Fri)"),
869
- items: z
870
- .array(z.object({
871
- title: z.string().describe("Subtask/checklist item text"),
872
- status: z
873
- .number()
874
- .min(0)
875
- .max(1)
876
- .optional()
877
- .describe("Status: 0=Unchecked (default), 1=Checked"),
878
- }))
879
- .optional()
880
- .describe("Array of subtask/checklist items"),
881
- }, async ({ title, projectId, content, dueDate, priority, tags, isAllDay, startDate, timeZone, reminders, repeat, items }) => {
882
- try {
883
- const client = await getClient();
884
- const task = await client.createTask({
885
- title,
886
- projectId,
887
- content,
888
- dueDate,
889
- priority,
890
- isAllDay,
891
- startDate,
892
- timeZone,
893
- reminders,
894
- repeatFlag: repeat,
895
- items,
896
- });
897
- // If tags are provided, update the task with tags
898
- let finalTask = task;
899
- if (tags && tags.length > 0) {
900
- finalTask = await client.updateTask(task.id, { tags });
901
- }
902
- return {
903
- content: [
904
- {
905
- type: "text",
906
- text: JSON.stringify({
907
- success: true,
908
- message: `Task "${finalTask.title}" created successfully!`,
909
- task: {
910
- id: finalTask.id,
911
- title: finalTask.title,
912
- projectId: finalTask.projectId,
913
- content: finalTask.content,
914
- priority: finalTask.priority,
915
- dueDate: finalTask.dueDate,
916
- startDate: finalTask.startDate,
917
- isAllDay: finalTask.isAllDay,
918
- timeZone: finalTask.timeZone,
919
- reminders: finalTask.reminders,
920
- repeat: finalTask.repeatFlag,
921
- status: finalTask.status,
922
- tags: finalTask.tags,
923
- items: finalTask.items?.map((item) => ({
924
- id: item.id,
925
- title: item.title,
926
- status: item.status,
927
- })),
928
- },
929
- }, null, 2),
930
- },
931
- ],
932
- };
933
- }
934
- catch (error) {
935
- const errorMessage = error instanceof Error ? error.message : String(error);
936
- return {
937
- content: [
938
- {
939
- type: "text",
940
- text: JSON.stringify({
941
- success: false,
942
- error: errorMessage,
943
- }, null, 2),
944
- },
945
- ],
946
- isError: true,
947
- };
948
- }
949
- });
950
- /**
951
- * Tool: Update task
952
- *
953
- * Updates an existing task.
954
- */
955
- server.tool("update_task", "Update an existing task in TickTick. Supports all task fields including scheduling, reminders, recurrence, and subtasks.", {
956
- taskId: z.string().describe("The ID of the task to update"),
957
- title: z.string().optional().describe("New title for the task"),
958
- content: z.string().optional().describe("New description/notes"),
959
- dueDate: z
960
- .string()
961
- .nullable()
962
- .optional()
963
- .describe("New due date (ISO 8601) or null to remove"),
964
- priority: z
965
- .number()
966
- .min(0)
967
- .max(5)
968
- .optional()
969
- .describe("New priority: 0=None, 1=Low, 3=Medium, 5=High"),
970
- tags: z.array(z.string()).optional().describe("Array of tag names"),
971
- projectId: z
972
- .string()
973
- .optional()
974
- .describe("Move task to a different project by specifying the target project ID"),
975
- isAllDay: z
976
- .boolean()
977
- .optional()
978
- .describe("Whether this is an all-day task (no specific time)"),
979
- startDate: z
980
- .string()
981
- .nullable()
982
- .optional()
983
- .describe("Start date in ISO 8601 format (e.g., 2024-01-15T09:00:00+0000) or null to remove"),
984
- timeZone: z
985
- .string()
986
- .optional()
987
- .describe("IANA timezone (e.g., 'America/New_York', 'Europe/London')"),
988
- reminders: z
989
- .array(z.string())
990
- .nullable()
991
- .optional()
992
- .describe("Array of reminder strings in iCalendar TRIGGER format (e.g., 'TRIGGER:P0DT9H0M0S' for 9:00 AM, 'TRIGGER:-PT15M' for 15 minutes before) or null to clear all reminders"),
993
- repeat: z
994
- .string()
995
- .nullable()
996
- .optional()
997
- .describe("Recurrence rule in RRULE format (e.g., 'RRULE:FREQ=DAILY;INTERVAL=1' for daily, 'RRULE:FREQ=WEEKLY;BYDAY=MO,WE,FR' for Mon/Wed/Fri) or null to remove recurrence"),
998
- items: z
999
- .array(z.object({
1000
- title: z.string().describe("Subtask/checklist item text"),
1001
- status: z
1002
- .number()
1003
- .min(0)
1004
- .max(1)
1005
- .optional()
1006
- .describe("Status: 0=Unchecked (default), 1=Checked"),
1007
- }))
1008
- .optional()
1009
- .describe("Array of subtask/checklist items. Note: This replaces existing items when provided."),
1010
- }, async ({ taskId, title, content, dueDate, priority, tags, projectId, isAllDay, startDate, timeZone, reminders, repeat, items }) => {
1011
- try {
1012
- const client = await getClient();
1013
- const task = await client.updateTask(taskId, {
1014
- title,
1015
- content,
1016
- dueDate,
1017
- priority,
1018
- tags,
1019
- projectId,
1020
- isAllDay,
1021
- startDate,
1022
- timeZone,
1023
- reminders: reminders === null ? undefined : reminders,
1024
- repeatFlag: repeat === null ? "" : repeat,
1025
- items,
1026
- });
1027
- return {
1028
- content: [
1029
- {
1030
- type: "text",
1031
- text: JSON.stringify({
1032
- success: true,
1033
- message: `Task "${task.title}" updated successfully!`,
1034
- task: {
1035
- id: task.id,
1036
- title: task.title,
1037
- projectId: task.projectId,
1038
- content: task.content,
1039
- priority: task.priority,
1040
- dueDate: task.dueDate,
1041
- startDate: task.startDate,
1042
- isAllDay: task.isAllDay,
1043
- timeZone: task.timeZone,
1044
- reminders: task.reminders,
1045
- repeat: task.repeatFlag,
1046
- status: task.status,
1047
- tags: task.tags,
1048
- items: task.items?.map((item) => ({
1049
- id: item.id,
1050
- title: item.title,
1051
- status: item.status,
1052
- })),
1053
- },
1054
- }, null, 2),
1055
- },
1056
- ],
1057
- };
1058
- }
1059
- catch (error) {
1060
- const errorMessage = error instanceof Error ? error.message : String(error);
1061
- return {
1062
- content: [
1063
- {
1064
- type: "text",
1065
- text: JSON.stringify({
1066
- success: false,
1067
- error: errorMessage,
1068
- }, null, 2),
1069
- },
1070
- ],
1071
- isError: true,
1072
- };
1073
- }
1074
- });
1075
- /**
1076
- * Tool: Complete task
1077
- *
1078
- * Marks a task as complete.
1079
- */
1080
- server.tool("complete_task", "Mark a task as complete in TickTick.", {
1081
- projectId: z.string().describe("The project ID containing the task"),
1082
- taskId: z.string().describe("The ID of the task to complete"),
1083
- }, async ({ projectId, taskId }) => {
1084
- try {
1085
- const client = await getClient();
1086
- await client.completeTask(projectId, taskId);
1087
- return {
1088
- content: [
1089
- {
1090
- type: "text",
1091
- text: JSON.stringify({
1092
- success: true,
1093
- message: "Task completed successfully!",
1094
- }, null, 2),
1095
- },
1096
- ],
1097
- };
1098
- }
1099
- catch (error) {
1100
- const errorMessage = error instanceof Error ? error.message : String(error);
1101
- return {
1102
- content: [
1103
- {
1104
- type: "text",
1105
- text: JSON.stringify({
1106
- success: false,
1107
- error: errorMessage,
1108
- }, null, 2),
1109
- },
1110
- ],
1111
- isError: true,
1112
- };
1113
- }
1114
- });
1115
- /**
1116
- * Tool: Delete task
1117
- *
1118
- * Deletes a task.
1119
- */
1120
- server.tool("delete_task", "Delete a task from TickTick.", {
1121
- projectId: z.string().describe("The project ID containing the task"),
1122
- taskId: z.string().describe("The ID of the task to delete"),
1123
- }, async ({ projectId, taskId }) => {
1124
- try {
1125
- const client = await getClient();
1126
- await client.deleteTask(projectId, taskId);
1127
- return {
1128
- content: [
1129
- {
1130
- type: "text",
1131
- text: JSON.stringify({
1132
- success: true,
1133
- message: "Task deleted successfully!",
1134
- }, null, 2),
1135
- },
1136
- ],
1137
- };
1138
- }
1139
- catch (error) {
1140
- const errorMessage = error instanceof Error ? error.message : String(error);
1141
- return {
1142
- content: [
1143
- {
1144
- type: "text",
1145
- text: JSON.stringify({
1146
- success: false,
1147
- error: errorMessage,
1148
- }, null, 2),
1149
- },
1150
- ],
1151
- isError: true,
1152
- };
1153
- }
1154
- });
1155
- /**
1156
- * Tool: Get task
1157
- *
1158
- * Gets a specific task by ID.
1159
- */
1160
- server.tool("get_task", "Get a specific task from TickTick.", {
1161
- projectId: z.string().describe("The project ID containing the task"),
1162
- taskId: z.string().describe("The ID of the task to retrieve"),
1163
- }, async ({ projectId, taskId }) => {
1164
- try {
1165
- const client = await getClient();
1166
- const task = await client.getTask(projectId, taskId);
1167
- return {
1168
- content: [
1169
- {
1170
- type: "text",
1171
- text: JSON.stringify({
1172
- success: true,
1173
- task: {
1174
- id: task.id,
1175
- title: task.title,
1176
- projectId: task.projectId,
1177
- content: task.content,
1178
- priority: task.priority,
1179
- status: task.status,
1180
- dueDate: task.dueDate,
1181
- startDate: task.startDate,
1182
- isAllDay: task.isAllDay,
1183
- tags: task.tags,
1184
- items: task.items.map((item) => ({
1185
- id: item.id,
1186
- title: item.title,
1187
- status: item.status,
1188
- })),
1189
- createdTime: task.createdTime,
1190
- modifiedTime: task.modifiedTime,
1191
- },
1192
- }, null, 2),
1193
- },
1194
- ],
1195
- };
1196
- }
1197
- catch (error) {
1198
- const errorMessage = error instanceof Error ? error.message : String(error);
1199
- return {
1200
- content: [
1201
- {
1202
- type: "text",
1203
- text: JSON.stringify({
1204
- success: false,
1205
- error: errorMessage,
1206
- }, null, 2),
1207
- },
1208
- ],
1209
- isError: true,
1210
- };
1211
- }
1212
- });
1213
- /**
1214
- * Tool: Batch create tasks
1215
- *
1216
- * Creates multiple tasks at once.
1217
- */
1218
- server.tool("batch_create_tasks", "Create multiple tasks at once in TickTick. More efficient than creating tasks one by one.", {
1219
- tasks: z
1220
- .array(z.object({
1221
- title: z.string().describe("The title of the task (required)"),
1222
- projectId: z
1223
- .string()
1224
- .optional()
1225
- .describe("The project ID (defaults to inbox if not specified)"),
1226
- content: z.string().optional().describe("Task description/notes"),
1227
- priority: z
1228
- .number()
1229
- .min(0)
1230
- .max(5)
1231
- .optional()
1232
- .describe("Priority: 0=None, 1=Low, 3=Medium, 5=High"),
1233
- dueDate: z
1234
- .string()
1235
- .optional()
1236
- .describe("Due date in ISO 8601 format (e.g., 2024-01-15T17:00:00+0000)"),
1237
- }))
1238
- .describe("Array of task objects to create"),
1239
- }, async ({ tasks }) => {
1240
- try {
1241
- const client = await getClient();
1242
- const createdTasks = await client.batchCreateTasks(tasks);
1243
- return {
1244
- content: [
1245
- {
1246
- type: "text",
1247
- text: JSON.stringify({
1248
- success: true,
1249
- message: `Successfully created ${createdTasks.length} tasks!`,
1250
- count: createdTasks.length,
1251
- tasks: createdTasks.map((t) => ({
1252
- id: t.id,
1253
- title: t.title,
1254
- projectId: t.projectId,
1255
- content: t.content,
1256
- priority: t.priority,
1257
- dueDate: t.dueDate,
1258
- status: t.status,
1259
- })),
1260
- }, null, 2),
1261
- },
1262
- ],
1263
- };
1264
- }
1265
- catch (error) {
1266
- const errorMessage = error instanceof Error ? error.message : String(error);
1267
- return {
1268
- content: [
1269
- {
1270
- type: "text",
1271
- text: JSON.stringify({
1272
- success: false,
1273
- error: errorMessage,
1274
- }, null, 2),
1275
- },
1276
- ],
1277
- isError: true,
1278
- };
1279
- }
1280
- });
1281
- /**
1282
- * Tool: Get tasks due soon
1283
- *
1284
- * Returns tasks due within a specified number of days across all projects.
1285
- */
1286
- server.tool("get_tasks_due_soon", "Get tasks that are due soon (within the next N days). Useful for finding urgent work across all projects.", {
1287
- days: z.number().optional().describe("Number of days to look ahead (default: 7)"),
1288
- includeOverdue: z.boolean().optional().describe("Include past-due tasks (default: true)"),
1289
- projectId: z.string().optional().describe("Optional: limit to a specific project"),
1290
- status: z.enum(['active', 'all']).optional().describe("Task status filter (default: 'active')"),
1291
- }, async ({ days = 7, includeOverdue = true, projectId, status = 'active' }) => {
1292
- try {
1293
- const client = await getClient();
1294
- // Calculate date range
1295
- const now = new Date();
1296
- const futureDate = new Date();
1297
- futureDate.setDate(now.getDate() + days);
1298
- let allTasks = [];
1299
- if (projectId) {
1300
- // Fetch tasks from specific project
1301
- const data = await client.getProjectWithTasks(projectId);
1302
- allTasks = data.tasks.map(t => ({
1303
- ...t,
1304
- projectName: data.project.name,
1305
- }));
1306
- }
1307
- else {
1308
- // Fetch all projects and their tasks
1309
- const projects = await client.listProjects();
1310
- for (const project of projects) {
1311
- if (!project.closed) { // Skip archived projects
1312
- try {
1313
- const data = await client.getProjectWithTasks(project.id);
1314
- const tasksWithProject = data.tasks.map(t => ({
1315
- ...t,
1316
- projectName: project.name,
1317
- }));
1318
- allTasks = allTasks.concat(tasksWithProject);
1319
- }
1320
- catch {
1321
- // Skip projects that can't be fetched
1322
- continue;
1323
- }
1324
- }
1325
- }
1326
- }
1327
- // Filter tasks
1328
- let filteredTasks = allTasks;
1329
- // Filter by status
1330
- if (status === 'active') {
1331
- filteredTasks = filteredTasks.filter(t => t.status === 0);
1332
- }
1333
- // Filter by due date
1334
- filteredTasks = filteredTasks.filter(t => {
1335
- if (!t.dueDate)
1336
- return false;
1337
- const dueDate = new Date(t.dueDate);
1338
- const isOverdue = dueDate < now;
1339
- const isDueSoon = dueDate >= now && dueDate <= futureDate;
1340
- if (includeOverdue && isOverdue)
1341
- return true;
1342
- if (isDueSoon)
1343
- return true;
1344
- return false;
1345
- });
1346
- // Sort by due date ascending (earliest first)
1347
- filteredTasks.sort((a, b) => {
1348
- const aDate = new Date(a.dueDate).getTime();
1349
- const bDate = new Date(b.dueDate).getTime();
1350
- return aDate - bDate;
1351
- });
1352
- return {
1353
- content: [
1354
- {
1355
- type: "text",
1356
- text: JSON.stringify({
1357
- success: true,
1358
- count: filteredTasks.length,
1359
- daysAhead: days,
1360
- tasks: filteredTasks.map(t => ({
1361
- id: t.id,
1362
- title: t.title,
1363
- projectId: t.projectId,
1364
- projectName: t.projectName,
1365
- dueDate: t.dueDate,
1366
- priority: t.priority,
1367
- status: t.status,
1368
- content: t.content,
1369
- tags: t.tags,
1370
- })),
1371
- }, null, 2),
1372
- },
1373
- ],
1374
- };
1375
- }
1376
- catch (error) {
1377
- const errorMessage = error instanceof Error ? error.message : String(error);
1378
- return {
1379
- content: [
1380
- {
1381
- type: "text",
1382
- text: JSON.stringify({
1383
- success: false,
1384
- error: errorMessage,
1385
- }, null, 2),
1386
- },
1387
- ],
1388
- isError: true,
1389
- };
1390
- }
1391
- });
1392
- /**
1393
- * Tool: Search tasks
1394
- *
1395
- * Search for tasks by keyword across all projects.
1396
- */
1397
- server.tool("search_tasks", "Search for tasks by keyword across all projects. Useful for finding tasks by text content.", {
1398
- query: z.string().describe("Search term (required)"),
1399
- searchIn: z.array(z.enum(['title', 'content', 'desc'])).optional().describe("Fields to search in (default: ['title', 'content'])"),
1400
- projectIds: z.array(z.string()).optional().describe("Optional: limit to specific project IDs"),
1401
- status: z.enum(['active', 'completed', 'all']).optional().describe("Task status filter (default: 'active')"),
1402
- caseSensitive: z.boolean().optional().describe("Case-sensitive search (default: false)"),
1403
- limit: z.number().optional().describe("Maximum results to return (default: 20)"),
1404
- }, async ({ query, searchIn = ['title', 'content'], projectIds, status = 'active', caseSensitive = false, limit = 20 }) => {
1405
- try {
1406
- const client = await getClient();
1407
- const searchQuery = caseSensitive ? query : query.toLowerCase();
1408
- let allTasks = [];
1409
- if (projectIds && projectIds.length > 0) {
1410
- // Search in specific projects
1411
- for (const projectId of projectIds) {
1412
- try {
1413
- const data = await client.getProjectWithTasks(projectId);
1414
- const tasksWithProject = data.tasks.map(t => ({
1415
- ...t,
1416
- projectName: data.project.name,
1417
- }));
1418
- allTasks = allTasks.concat(tasksWithProject);
1419
- }
1420
- catch {
1421
- continue;
1422
- }
1423
- }
1424
- }
1425
- else {
1426
- // Search all projects
1427
- const projects = await client.listProjects();
1428
- for (const project of projects) {
1429
- if (!project.closed) {
1430
- try {
1431
- const data = await client.getProjectWithTasks(project.id);
1432
- const tasksWithProject = data.tasks.map(t => ({
1433
- ...t,
1434
- projectName: project.name,
1435
- }));
1436
- allTasks = allTasks.concat(tasksWithProject);
1437
- }
1438
- catch {
1439
- continue;
1440
- }
1441
- }
1442
- }
1443
- }
1444
- // Filter by status
1445
- let filteredTasks = allTasks;
1446
- if (status === 'active') {
1447
- filteredTasks = filteredTasks.filter(t => t.status === 0);
1448
- }
1449
- else if (status === 'completed') {
1450
- filteredTasks = filteredTasks.filter(t => t.status === 2);
1451
- }
1452
- // Search and collect matches
1453
- const matches = [];
1454
- for (const task of filteredTasks) {
1455
- const matchedIn = [];
1456
- for (const field of searchIn) {
1457
- let fieldValue = '';
1458
- if (field === 'title')
1459
- fieldValue = task.title || '';
1460
- else if (field === 'content')
1461
- fieldValue = task.content || '';
1462
- else if (field === 'desc')
1463
- fieldValue = task.desc || '';
1464
- const searchText = caseSensitive ? fieldValue : fieldValue.toLowerCase();
1465
- if (searchText.includes(searchQuery)) {
1466
- matchedIn.push(field);
1467
- }
1468
- }
1469
- if (matchedIn.length > 0) {
1470
- matches.push({
1471
- ...task,
1472
- matchedIn,
1473
- });
1474
- }
1475
- if (matches.length >= limit)
1476
- break;
1477
- }
1478
- return {
1479
- content: [
1480
- {
1481
- type: "text",
1482
- text: JSON.stringify({
1483
- success: true,
1484
- query: query,
1485
- count: matches.length,
1486
- tasks: matches.map(t => ({
1487
- id: t.id,
1488
- title: t.title,
1489
- projectId: t.projectId,
1490
- projectName: t.projectName,
1491
- content: t.content,
1492
- dueDate: t.dueDate,
1493
- priority: t.priority,
1494
- status: t.status,
1495
- tags: t.tags,
1496
- matchedIn: t.matchedIn,
1497
- })),
1498
- }, null, 2),
1499
- },
1500
- ],
1501
- };
1502
- }
1503
- catch (error) {
1504
- const errorMessage = error instanceof Error ? error.message : String(error);
1505
- return {
1506
- content: [
1507
- {
1508
- type: "text",
1509
- text: JSON.stringify({
1510
- success: false,
1511
- error: errorMessage,
1512
- }, null, 2),
1513
- },
1514
- ],
1515
- isError: true,
1516
- };
1517
- }
1518
- });
1519
- /**
1520
- * Tool: Get high priority tasks
1521
- *
1522
- * Returns high priority tasks across all projects.
1523
- */
1524
- server.tool("get_high_priority_tasks", "Get high priority tasks across all projects. Useful for finding the most important work.", {
1525
- minPriority: z.number().optional().describe("Minimum priority level: 0=None, 1=Low, 3=Medium, 5=High (default: 3)"),
1526
- projectId: z.string().optional().describe("Optional: limit to a specific project"),
1527
- includeCompleted: z.boolean().optional().describe("Include completed tasks (default: false)"),
1528
- limit: z.number().optional().describe("Maximum results to return (default: 10)"),
1529
- }, async ({ minPriority = 3, projectId, includeCompleted = false, limit = 10 }) => {
1530
- try {
1531
- const client = await getClient();
1532
- let allTasks = [];
1533
- if (projectId) {
1534
- // Fetch tasks from specific project
1535
- const data = await client.getProjectWithTasks(projectId);
1536
- allTasks = data.tasks.map(t => ({
1537
- ...t,
1538
- projectName: data.project.name,
1539
- }));
1540
- }
1541
- else {
1542
- // Fetch all projects and their tasks
1543
- const projects = await client.listProjects();
1544
- for (const project of projects) {
1545
- if (!project.closed) {
1546
- try {
1547
- const data = await client.getProjectWithTasks(project.id);
1548
- const tasksWithProject = data.tasks.map(t => ({
1549
- ...t,
1550
- projectName: project.name,
1551
- }));
1552
- allTasks = allTasks.concat(tasksWithProject);
1553
- }
1554
- catch {
1555
- continue;
1556
- }
1557
- }
1558
- }
1559
- }
1560
- // Filter tasks
1561
- let filteredTasks = allTasks;
1562
- // Filter by completion status
1563
- if (!includeCompleted) {
1564
- filteredTasks = filteredTasks.filter(t => t.status === 0);
1565
- }
1566
- // Filter by priority
1567
- filteredTasks = filteredTasks.filter(t => t.priority >= minPriority);
1568
- // Sort by priority desc (high first), then by dueDate asc (earliest first)
1569
- filteredTasks.sort((a, b) => {
1570
- // First sort by priority (descending)
1571
- if (a.priority !== b.priority) {
1572
- return b.priority - a.priority;
1573
- }
1574
- // Then by due date (ascending, nulls last)
1575
- if (a.dueDate && b.dueDate) {
1576
- return new Date(a.dueDate).getTime() - new Date(b.dueDate).getTime();
1577
- }
1578
- if (a.dueDate)
1579
- return -1;
1580
- if (b.dueDate)
1581
- return 1;
1582
- return 0;
1583
- });
1584
- // Apply limit
1585
- filteredTasks = filteredTasks.slice(0, limit);
1586
- return {
1587
- content: [
1588
- {
1589
- type: "text",
1590
- text: JSON.stringify({
1591
- success: true,
1592
- count: filteredTasks.length,
1593
- minPriority,
1594
- tasks: filteredTasks.map(t => ({
1595
- id: t.id,
1596
- title: t.title,
1597
- projectId: t.projectId,
1598
- projectName: t.projectName,
1599
- dueDate: t.dueDate,
1600
- priority: t.priority,
1601
- status: t.status,
1602
- content: t.content,
1603
- tags: t.tags,
1604
- })),
1605
- }, null, 2),
1606
- },
1607
- ],
1608
- };
1609
- }
1610
- catch (error) {
1611
- const errorMessage = error instanceof Error ? error.message : String(error);
1612
- return {
1613
- content: [
1614
- {
1615
- type: "text",
1616
- text: JSON.stringify({
1617
- success: false,
1618
- error: errorMessage,
1619
- }, null, 2),
1620
- },
1621
- ],
1622
- isError: true,
1623
- };
1624
- }
1625
- });
81
+ registerListTasksInProjectTool(server, getClient);
82
+ registerCreateTaskTool(server, getClient);
83
+ registerUpdateTaskTool(server, getClient);
84
+ registerCompleteTaskTool(server, getClient);
85
+ registerDeleteTaskTool(server, getClient);
86
+ registerGetTaskTool(server, getClient);
87
+ registerBatchCreateTasksTool(server, getClient);
88
+ registerGetTasksDueSoonTool(server, getClient);
89
+ registerSearchTasksTool(server, getClient);
90
+ registerGetHighPriorityTasksTool(server, getClient);
1626
91
  // =============================================================================
1627
92
  // Main Entry Point
1628
93
  // =============================================================================