ticktick-mcp 0.1.0 → 0.2.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/dist/index.js +508 -22
- package/dist/index.js.map +1 -1
- package/dist/sdk/__tests__/client.test.d.ts +2 -0
- package/dist/sdk/__tests__/client.test.d.ts.map +1 -0
- package/dist/sdk/__tests__/client.test.js +251 -0
- package/dist/sdk/__tests__/client.test.js.map +1 -0
- package/dist/sdk/__tests__/errors.test.d.ts +2 -0
- package/dist/sdk/__tests__/errors.test.d.ts.map +1 -0
- package/dist/sdk/__tests__/errors.test.js +164 -0
- package/dist/sdk/__tests__/errors.test.js.map +1 -0
- package/dist/sdk/__tests__/types.test.d.ts +2 -0
- package/dist/sdk/__tests__/types.test.d.ts.map +1 -0
- package/dist/sdk/__tests__/types.test.js +134 -0
- package/dist/sdk/__tests__/types.test.js.map +1 -0
- package/dist/sdk/client.d.ts +10 -2
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +14 -3
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/index.d.ts +5 -3
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +10 -2
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/types.d.ts +215 -23
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/types.js +169 -6
- package/dist/sdk/types.js.map +1 -1
- package/package.json +14 -4
package/dist/index.js
CHANGED
|
@@ -329,10 +329,37 @@ server.tool("get_user", "Get the current authenticated user's information from T
|
|
|
329
329
|
*
|
|
330
330
|
* Returns all projects for the authenticated user.
|
|
331
331
|
*/
|
|
332
|
-
server.tool("list_projects", "List all projects in the user's TickTick account.
|
|
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 }) => {
|
|
333
339
|
try {
|
|
334
340
|
const client = await getClient();
|
|
335
|
-
|
|
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
|
+
}
|
|
336
363
|
return {
|
|
337
364
|
content: [
|
|
338
365
|
{
|
|
@@ -489,7 +516,7 @@ server.tool("create_project", "Create a new project in TickTick.", {
|
|
|
489
516
|
const project = await client.createProject({
|
|
490
517
|
name,
|
|
491
518
|
color,
|
|
492
|
-
viewMode,
|
|
519
|
+
viewMode: viewMode,
|
|
493
520
|
});
|
|
494
521
|
return {
|
|
495
522
|
content: [
|
|
@@ -547,7 +574,7 @@ server.tool("update_project", "Update an existing project in TickTick.", {
|
|
|
547
574
|
const project = await client.updateProject(projectId, {
|
|
548
575
|
name,
|
|
549
576
|
color,
|
|
550
|
-
viewMode,
|
|
577
|
+
viewMode: viewMode,
|
|
551
578
|
});
|
|
552
579
|
return {
|
|
553
580
|
content: [
|
|
@@ -632,20 +659,134 @@ server.tool("delete_project", "Delete a project from TickTick.", {
|
|
|
632
659
|
* Returns all tasks in a specific project (without project metadata).
|
|
633
660
|
* This is a convenience tool that wraps getProjectWithTasks but only returns tasks.
|
|
634
661
|
*/
|
|
635
|
-
server.tool("list_tasks_in_project", "List all tasks in a specific project. Returns only the tasks array without project metadata.", {
|
|
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.", {
|
|
636
663
|
projectId: z.string().describe("The ID of the project to list tasks from"),
|
|
637
|
-
|
|
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 }) => {
|
|
638
677
|
try {
|
|
639
678
|
const client = await getClient();
|
|
640
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
|
+
}
|
|
641
781
|
return {
|
|
642
782
|
content: [
|
|
643
783
|
{
|
|
644
784
|
type: "text",
|
|
645
785
|
text: JSON.stringify({
|
|
646
786
|
success: true,
|
|
647
|
-
|
|
648
|
-
|
|
787
|
+
total: data.tasks.length,
|
|
788
|
+
count: filteredTasks.length,
|
|
789
|
+
tasks: filteredTasks.map((t) => ({
|
|
649
790
|
id: t.id,
|
|
650
791
|
title: t.title,
|
|
651
792
|
content: t.content,
|
|
@@ -653,7 +794,7 @@ server.tool("list_tasks_in_project", "List all tasks in a specific project. Retu
|
|
|
653
794
|
status: t.status,
|
|
654
795
|
dueDate: t.dueDate,
|
|
655
796
|
startDate: t.startDate,
|
|
656
|
-
|
|
797
|
+
isAllDay: t.isAllDay,
|
|
657
798
|
tags: t.tags,
|
|
658
799
|
items: t.items?.map((item) => ({
|
|
659
800
|
id: item.id,
|
|
@@ -705,7 +846,7 @@ server.tool("create_task", "Create a new task in TickTick.", {
|
|
|
705
846
|
.optional()
|
|
706
847
|
.describe("Priority: 0=None, 1=Low, 3=Medium, 5=High"),
|
|
707
848
|
tags: z.array(z.string()).optional().describe("Array of tag names"),
|
|
708
|
-
|
|
849
|
+
isAllDay: z
|
|
709
850
|
.boolean()
|
|
710
851
|
.optional()
|
|
711
852
|
.describe("Whether this is an all-day task (no specific time)"),
|
|
@@ -737,7 +878,7 @@ server.tool("create_task", "Create a new task in TickTick.", {
|
|
|
737
878
|
}))
|
|
738
879
|
.optional()
|
|
739
880
|
.describe("Array of subtask/checklist items"),
|
|
740
|
-
}, async ({ title, projectId, content, dueDate, priority, tags,
|
|
881
|
+
}, async ({ title, projectId, content, dueDate, priority, tags, isAllDay, startDate, timeZone, reminders, repeat, items }) => {
|
|
741
882
|
try {
|
|
742
883
|
const client = await getClient();
|
|
743
884
|
const task = await client.createTask({
|
|
@@ -746,11 +887,11 @@ server.tool("create_task", "Create a new task in TickTick.", {
|
|
|
746
887
|
content,
|
|
747
888
|
dueDate,
|
|
748
889
|
priority,
|
|
749
|
-
|
|
890
|
+
isAllDay,
|
|
750
891
|
startDate,
|
|
751
892
|
timeZone,
|
|
752
893
|
reminders,
|
|
753
|
-
repeat,
|
|
894
|
+
repeatFlag: repeat,
|
|
754
895
|
items,
|
|
755
896
|
});
|
|
756
897
|
// If tags are provided, update the task with tags
|
|
@@ -773,10 +914,10 @@ server.tool("create_task", "Create a new task in TickTick.", {
|
|
|
773
914
|
priority: finalTask.priority,
|
|
774
915
|
dueDate: finalTask.dueDate,
|
|
775
916
|
startDate: finalTask.startDate,
|
|
776
|
-
|
|
917
|
+
isAllDay: finalTask.isAllDay,
|
|
777
918
|
timeZone: finalTask.timeZone,
|
|
778
919
|
reminders: finalTask.reminders,
|
|
779
|
-
repeat: finalTask.
|
|
920
|
+
repeat: finalTask.repeatFlag,
|
|
780
921
|
status: finalTask.status,
|
|
781
922
|
tags: finalTask.tags,
|
|
782
923
|
items: finalTask.items?.map((item) => ({
|
|
@@ -831,7 +972,7 @@ server.tool("update_task", "Update an existing task in TickTick. Supports all ta
|
|
|
831
972
|
.string()
|
|
832
973
|
.optional()
|
|
833
974
|
.describe("Move task to a different project by specifying the target project ID"),
|
|
834
|
-
|
|
975
|
+
isAllDay: z
|
|
835
976
|
.boolean()
|
|
836
977
|
.optional()
|
|
837
978
|
.describe("Whether this is an all-day task (no specific time)"),
|
|
@@ -866,7 +1007,7 @@ server.tool("update_task", "Update an existing task in TickTick. Supports all ta
|
|
|
866
1007
|
}))
|
|
867
1008
|
.optional()
|
|
868
1009
|
.describe("Array of subtask/checklist items. Note: This replaces existing items when provided."),
|
|
869
|
-
}, async ({ taskId, title, content, dueDate, priority, tags, projectId,
|
|
1010
|
+
}, async ({ taskId, title, content, dueDate, priority, tags, projectId, isAllDay, startDate, timeZone, reminders, repeat, items }) => {
|
|
870
1011
|
try {
|
|
871
1012
|
const client = await getClient();
|
|
872
1013
|
const task = await client.updateTask(taskId, {
|
|
@@ -876,11 +1017,11 @@ server.tool("update_task", "Update an existing task in TickTick. Supports all ta
|
|
|
876
1017
|
priority,
|
|
877
1018
|
tags,
|
|
878
1019
|
projectId,
|
|
879
|
-
|
|
1020
|
+
isAllDay,
|
|
880
1021
|
startDate,
|
|
881
1022
|
timeZone,
|
|
882
1023
|
reminders: reminders === null ? undefined : reminders,
|
|
883
|
-
|
|
1024
|
+
repeatFlag: repeat === null ? "" : repeat,
|
|
884
1025
|
items,
|
|
885
1026
|
});
|
|
886
1027
|
return {
|
|
@@ -898,10 +1039,10 @@ server.tool("update_task", "Update an existing task in TickTick. Supports all ta
|
|
|
898
1039
|
priority: task.priority,
|
|
899
1040
|
dueDate: task.dueDate,
|
|
900
1041
|
startDate: task.startDate,
|
|
901
|
-
|
|
1042
|
+
isAllDay: task.isAllDay,
|
|
902
1043
|
timeZone: task.timeZone,
|
|
903
1044
|
reminders: task.reminders,
|
|
904
|
-
repeat: task.
|
|
1045
|
+
repeat: task.repeatFlag,
|
|
905
1046
|
status: task.status,
|
|
906
1047
|
tags: task.tags,
|
|
907
1048
|
items: task.items?.map((item) => ({
|
|
@@ -1038,7 +1179,7 @@ server.tool("get_task", "Get a specific task from TickTick.", {
|
|
|
1038
1179
|
status: task.status,
|
|
1039
1180
|
dueDate: task.dueDate,
|
|
1040
1181
|
startDate: task.startDate,
|
|
1041
|
-
|
|
1182
|
+
isAllDay: task.isAllDay,
|
|
1042
1183
|
tags: task.tags,
|
|
1043
1184
|
items: task.items.map((item) => ({
|
|
1044
1185
|
id: item.id,
|
|
@@ -1137,6 +1278,351 @@ server.tool("batch_create_tasks", "Create multiple tasks at once in TickTick. Mo
|
|
|
1137
1278
|
};
|
|
1138
1279
|
}
|
|
1139
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
|
+
});
|
|
1140
1626
|
// =============================================================================
|
|
1141
1627
|
// Main Entry Point
|
|
1142
1628
|
// =============================================================================
|