@doist/todoist-ai 4.15.1 → 4.16.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/dist/filter-helpers.d.ts +1 -1
- package/dist/index.d.ts +175 -175
- package/dist/index.js +61 -81
- package/dist/main.js +15 -23
- package/dist/mcp-helpers.d.ts +4 -4
- package/dist/mcp-server-6tm7Rhyz.js +2840 -0
- package/dist/todoist-tool.d.ts +2 -2
- package/dist/tool-helpers.d.ts +1 -1
- package/dist/tools/add-comments.d.ts +1 -1
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +4 -4
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +1 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +4 -4
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/complete-tasks.d.ts +1 -1
- package/dist/tools/complete-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +3 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/fetch.d.ts +1 -1
- package/dist/tools/find-activity.d.ts +5 -5
- package/dist/tools/find-activity.d.ts.map +1 -1
- package/dist/tools/find-comments.d.ts +2 -2
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +3 -3
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-project-collaborators.d.ts +2 -2
- package/dist/tools/find-projects.d.ts +1 -1
- package/dist/tools/find-projects.d.ts.map +1 -1
- package/dist/tools/find-sections.d.ts +1 -1
- package/dist/tools/find-sections.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.d.ts +1 -1
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks.d.ts +3 -3
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/get-overview.d.ts +1 -1
- package/dist/tools/manage-assignments.d.ts +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/update-comments.d.ts +4 -4
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +1 -1
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +4 -4
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +7 -7
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/user-info.d.ts +1 -1
- package/dist/utils/assignment-validator.d.ts +2 -2
- package/dist/utils/response-builders.d.ts +1 -3
- package/dist/utils/response-builders.d.ts.map +1 -1
- package/dist/utils/test-helpers.d.ts +1 -1
- package/dist/utils/user-resolver.d.ts +1 -1
- package/package.json +11 -9
- package/dist/filter-helpers.js +0 -79
- package/dist/mcp-helpers.js +0 -71
- package/dist/mcp-server.js +0 -142
- package/dist/todoist-tool.js +0 -1
- package/dist/tool-helpers.js +0 -125
- package/dist/tool-helpers.test.d.ts +0 -2
- package/dist/tool-helpers.test.d.ts.map +0 -1
- package/dist/tool-helpers.test.js +0 -223
- package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-comments.test.js +0 -241
- package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-projects.test.js +0 -174
- package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-sections.test.js +0 -185
- package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-tasks.test.js +0 -533
- package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
- package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
- package/dist/tools/__tests__/assignment-integration.test.js +0 -428
- package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/complete-tasks.test.js +0 -206
- package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
- package/dist/tools/__tests__/delete-object.test.js +0 -110
- package/dist/tools/__tests__/fetch.test.d.ts +0 -2
- package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/tools/__tests__/fetch.test.js +0 -279
- package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
- package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-activity.test.js +0 -229
- package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-comments.test.js +0 -236
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-completed-tasks.test.js +0 -324
- package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-projects.test.js +0 -154
- package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-sections.test.js +0 -245
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
- package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks.test.js +0 -771
- package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
- package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/get-overview.test.js +0 -225
- package/dist/tools/__tests__/search.test.d.ts +0 -2
- package/dist/tools/__tests__/search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/search.test.js +0 -206
- package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-comments.test.js +0 -294
- package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-projects.test.js +0 -217
- package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-sections.test.js +0 -169
- package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-tasks.test.js +0 -788
- package/dist/tools/__tests__/user-info.test.d.ts +0 -2
- package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
- package/dist/tools/__tests__/user-info.test.js +0 -139
- package/dist/tools/add-comments.js +0 -79
- package/dist/tools/add-projects.js +0 -63
- package/dist/tools/add-sections.js +0 -61
- package/dist/tools/add-tasks.js +0 -160
- package/dist/tools/complete-tasks.js +0 -68
- package/dist/tools/delete-object.js +0 -79
- package/dist/tools/fetch.js +0 -102
- package/dist/tools/find-activity.js +0 -221
- package/dist/tools/find-comments.js +0 -143
- package/dist/tools/find-completed-tasks.js +0 -161
- package/dist/tools/find-project-collaborators.js +0 -151
- package/dist/tools/find-projects.js +0 -101
- package/dist/tools/find-sections.js +0 -96
- package/dist/tools/find-tasks-by-date.js +0 -198
- package/dist/tools/find-tasks.js +0 -329
- package/dist/tools/get-overview.js +0 -249
- package/dist/tools/manage-assignments.js +0 -337
- package/dist/tools/search.js +0 -65
- package/dist/tools/update-comments.js +0 -82
- package/dist/tools/update-projects.js +0 -84
- package/dist/tools/update-sections.js +0 -70
- package/dist/tools/update-tasks.js +0 -170
- package/dist/tools/user-info.js +0 -142
- package/dist/utils/assignment-validator.js +0 -253
- package/dist/utils/constants.js +0 -45
- package/dist/utils/duration-parser.js +0 -96
- package/dist/utils/duration-parser.test.d.ts +0 -2
- package/dist/utils/duration-parser.test.d.ts.map +0 -1
- package/dist/utils/duration-parser.test.js +0 -147
- package/dist/utils/labels.js +0 -18
- package/dist/utils/priorities.js +0 -20
- package/dist/utils/response-builders.js +0 -210
- package/dist/utils/sanitize-data.js +0 -37
- package/dist/utils/sanitize-data.test.d.ts +0 -2
- package/dist/utils/sanitize-data.test.d.ts.map +0 -1
- package/dist/utils/sanitize-data.test.js +0 -93
- package/dist/utils/test-helpers.js +0 -237
- package/dist/utils/tool-names.js +0 -40
- package/dist/utils/user-resolver.js +0 -179
package/dist/tools/fetch.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { getProjectUrl, getTaskUrl } from '@doist/todoist-api-typescript';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { getErrorOutput } from '../mcp-helpers.js';
|
|
4
|
-
import { mapProject, mapTask } from '../tool-helpers.js';
|
|
5
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
6
|
-
const ArgsSchema = {
|
|
7
|
-
id: z
|
|
8
|
-
.string()
|
|
9
|
-
.min(1)
|
|
10
|
-
.describe('A unique identifier for the document in the format "task:{id}" or "project:{id}".'),
|
|
11
|
-
};
|
|
12
|
-
/**
|
|
13
|
-
* OpenAI MCP fetch tool - retrieves the full contents of a task or project by ID.
|
|
14
|
-
*
|
|
15
|
-
* This tool follows the OpenAI MCP fetch tool specification:
|
|
16
|
-
* @see https://platform.openai.com/docs/mcp#fetch-tool
|
|
17
|
-
*/
|
|
18
|
-
const fetch = {
|
|
19
|
-
name: ToolNames.FETCH,
|
|
20
|
-
description: 'Fetch the full contents of a task or project by its ID. The ID should be in the format "task:{id}" or "project:{id}".',
|
|
21
|
-
parameters: ArgsSchema,
|
|
22
|
-
async execute(args, client) {
|
|
23
|
-
try {
|
|
24
|
-
const { id } = args;
|
|
25
|
-
// Parse the composite ID
|
|
26
|
-
const [type, objectId] = id.split(':', 2);
|
|
27
|
-
if (!objectId || (type !== 'task' && type !== 'project')) {
|
|
28
|
-
throw new Error('Invalid ID format. Expected "task:{id}" or "project:{id}". Example: "task:8485093748" or "project:6cfCcrrCFg2xP94Q"');
|
|
29
|
-
}
|
|
30
|
-
let result;
|
|
31
|
-
if (type === 'task') {
|
|
32
|
-
// Fetch task
|
|
33
|
-
const task = await client.getTask(objectId);
|
|
34
|
-
const mappedTask = mapTask(task);
|
|
35
|
-
// Build text content
|
|
36
|
-
const textParts = [mappedTask.content];
|
|
37
|
-
if (mappedTask.description) {
|
|
38
|
-
textParts.push(`\n\nDescription: ${mappedTask.description}`);
|
|
39
|
-
}
|
|
40
|
-
if (mappedTask.dueDate) {
|
|
41
|
-
textParts.push(`\nDue: ${mappedTask.dueDate}`);
|
|
42
|
-
}
|
|
43
|
-
if (mappedTask.labels.length > 0) {
|
|
44
|
-
textParts.push(`\nLabels: ${mappedTask.labels.join(', ')}`);
|
|
45
|
-
}
|
|
46
|
-
result = {
|
|
47
|
-
id: `task:${mappedTask.id}`,
|
|
48
|
-
title: mappedTask.content,
|
|
49
|
-
text: textParts.join(''),
|
|
50
|
-
url: getTaskUrl(mappedTask.id),
|
|
51
|
-
metadata: {
|
|
52
|
-
priority: mappedTask.priority,
|
|
53
|
-
projectId: mappedTask.projectId,
|
|
54
|
-
sectionId: mappedTask.sectionId,
|
|
55
|
-
parentId: mappedTask.parentId,
|
|
56
|
-
recurring: mappedTask.recurring,
|
|
57
|
-
duration: mappedTask.duration,
|
|
58
|
-
responsibleUid: mappedTask.responsibleUid,
|
|
59
|
-
assignedByUid: mappedTask.assignedByUid,
|
|
60
|
-
checked: mappedTask.checked,
|
|
61
|
-
completedAt: mappedTask.completedAt,
|
|
62
|
-
},
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
// Fetch project
|
|
67
|
-
const project = await client.getProject(objectId);
|
|
68
|
-
const mappedProject = mapProject(project);
|
|
69
|
-
// Build text content
|
|
70
|
-
const textParts = [mappedProject.name];
|
|
71
|
-
if (mappedProject.isShared) {
|
|
72
|
-
textParts.push('\n\nShared project');
|
|
73
|
-
}
|
|
74
|
-
if (mappedProject.isFavorite) {
|
|
75
|
-
textParts.push('\nFavorite: Yes');
|
|
76
|
-
}
|
|
77
|
-
result = {
|
|
78
|
-
id: `project:${mappedProject.id}`,
|
|
79
|
-
title: mappedProject.name,
|
|
80
|
-
text: textParts.join(''),
|
|
81
|
-
url: getProjectUrl(mappedProject.id),
|
|
82
|
-
metadata: {
|
|
83
|
-
color: mappedProject.color,
|
|
84
|
-
isFavorite: mappedProject.isFavorite,
|
|
85
|
-
isShared: mappedProject.isShared,
|
|
86
|
-
parentId: mappedProject.parentId,
|
|
87
|
-
inboxProject: mappedProject.inboxProject,
|
|
88
|
-
viewStyle: mappedProject.viewStyle,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
// Return as JSON-encoded string in a text content item (OpenAI MCP spec)
|
|
93
|
-
const jsonText = JSON.stringify(result);
|
|
94
|
-
return { content: [{ type: 'text', text: jsonText }] };
|
|
95
|
-
}
|
|
96
|
-
catch (error) {
|
|
97
|
-
const message = error instanceof Error ? error.message : 'An unknown error occurred';
|
|
98
|
-
return getErrorOutput(message);
|
|
99
|
-
}
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
export { fetch };
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
|
-
import { mapActivityEvent } from '../tool-helpers.js';
|
|
4
|
-
import { ApiLimits } from '../utils/constants.js';
|
|
5
|
-
import { summarizeList } from '../utils/response-builders.js';
|
|
6
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
7
|
-
const { FIND_TASKS, USER_INFO } = ToolNames;
|
|
8
|
-
const ArgsSchema = {
|
|
9
|
-
objectType: z
|
|
10
|
-
.enum(['task', 'project', 'comment'])
|
|
11
|
-
.optional()
|
|
12
|
-
.describe('Type of object to filter by.'),
|
|
13
|
-
objectId: z
|
|
14
|
-
.string()
|
|
15
|
-
.optional()
|
|
16
|
-
.describe('Filter by specific object ID (task, project, or comment).'),
|
|
17
|
-
eventType: z
|
|
18
|
-
.enum([
|
|
19
|
-
'added',
|
|
20
|
-
'updated',
|
|
21
|
-
'deleted',
|
|
22
|
-
'completed',
|
|
23
|
-
'uncompleted',
|
|
24
|
-
'archived',
|
|
25
|
-
'unarchived',
|
|
26
|
-
'shared',
|
|
27
|
-
'left',
|
|
28
|
-
])
|
|
29
|
-
.optional()
|
|
30
|
-
.describe('Type of event to filter by.'),
|
|
31
|
-
projectId: z.string().optional().describe('Filter events by parent project ID.'),
|
|
32
|
-
taskId: z.string().optional().describe('Filter events by parent task ID (for subtask events).'),
|
|
33
|
-
initiatorId: z.string().optional().describe('Filter by the user ID who initiated the event.'),
|
|
34
|
-
limit: z
|
|
35
|
-
.number()
|
|
36
|
-
.int()
|
|
37
|
-
.min(1)
|
|
38
|
-
.max(ApiLimits.ACTIVITY_MAX)
|
|
39
|
-
.default(ApiLimits.ACTIVITY_DEFAULT)
|
|
40
|
-
.describe('Maximum number of activity events to return.'),
|
|
41
|
-
cursor: z
|
|
42
|
-
.string()
|
|
43
|
-
.optional()
|
|
44
|
-
.describe('Pagination cursor for retrieving the next page of results.'),
|
|
45
|
-
};
|
|
46
|
-
const findActivity = {
|
|
47
|
-
name: ToolNames.FIND_ACTIVITY,
|
|
48
|
-
description: 'Retrieve recent activity logs to monitor and audit changes in Todoist. Shows events from all users by default (use initiatorId to filter by specific user). Track task completions, updates, deletions, project changes, and more with flexible filtering. Note: Date-based filtering is not supported by the Todoist API.',
|
|
49
|
-
parameters: ArgsSchema,
|
|
50
|
-
async execute(args, client) {
|
|
51
|
-
const { objectType, objectId, eventType, projectId, taskId, initiatorId, limit, cursor } = args;
|
|
52
|
-
// Build API arguments
|
|
53
|
-
const apiArgs = {
|
|
54
|
-
limit,
|
|
55
|
-
cursor: cursor ?? null,
|
|
56
|
-
};
|
|
57
|
-
// Add optional filters
|
|
58
|
-
if (objectType)
|
|
59
|
-
apiArgs.objectType = objectType;
|
|
60
|
-
if (objectId && objectId !== 'remove')
|
|
61
|
-
apiArgs.objectId = objectId;
|
|
62
|
-
if (eventType)
|
|
63
|
-
apiArgs.eventType = eventType;
|
|
64
|
-
if (projectId)
|
|
65
|
-
apiArgs.parentProjectId = projectId;
|
|
66
|
-
if (taskId)
|
|
67
|
-
apiArgs.parentItemId = taskId;
|
|
68
|
-
if (initiatorId)
|
|
69
|
-
apiArgs.initiatorId = initiatorId;
|
|
70
|
-
// Fetch activity logs from API
|
|
71
|
-
const { results, nextCursor } = await client.getActivityLogs(apiArgs);
|
|
72
|
-
const events = results.map(mapActivityEvent);
|
|
73
|
-
// Generate text content
|
|
74
|
-
const textContent = generateTextContent({
|
|
75
|
-
events,
|
|
76
|
-
args,
|
|
77
|
-
nextCursor,
|
|
78
|
-
});
|
|
79
|
-
return getToolOutput({
|
|
80
|
-
textContent,
|
|
81
|
-
structuredContent: {
|
|
82
|
-
events,
|
|
83
|
-
nextCursor,
|
|
84
|
-
totalCount: events.length,
|
|
85
|
-
hasMore: Boolean(nextCursor),
|
|
86
|
-
appliedFilters: args,
|
|
87
|
-
},
|
|
88
|
-
});
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
function generateTextContent({ events, args, nextCursor, }) {
|
|
92
|
-
// Generate subject description
|
|
93
|
-
let subject = 'Activity events';
|
|
94
|
-
// Build subject based on filters
|
|
95
|
-
const subjectParts = [];
|
|
96
|
-
if (args.eventType) {
|
|
97
|
-
subjectParts.push(`${args.eventType}`);
|
|
98
|
-
}
|
|
99
|
-
if (args.objectType) {
|
|
100
|
-
const objectLabel = args.objectType === 'task' ? 'tasks' : `${args.objectType}s`;
|
|
101
|
-
subjectParts.push(objectLabel);
|
|
102
|
-
}
|
|
103
|
-
if (subjectParts.length > 0) {
|
|
104
|
-
subject = `Activity: ${subjectParts.join(' ')}`;
|
|
105
|
-
}
|
|
106
|
-
// Generate filter hints
|
|
107
|
-
const filterHints = [];
|
|
108
|
-
if (args.objectId) {
|
|
109
|
-
filterHints.push(`object ID: ${args.objectId}`);
|
|
110
|
-
}
|
|
111
|
-
if (args.projectId) {
|
|
112
|
-
filterHints.push(`project: ${args.projectId}`);
|
|
113
|
-
}
|
|
114
|
-
if (args.taskId) {
|
|
115
|
-
filterHints.push(`task: ${args.taskId}`);
|
|
116
|
-
}
|
|
117
|
-
if (args.initiatorId) {
|
|
118
|
-
filterHints.push(`initiator: ${args.initiatorId}`);
|
|
119
|
-
}
|
|
120
|
-
// Generate helpful suggestions for empty results
|
|
121
|
-
const zeroReasonHints = [];
|
|
122
|
-
if (events.length === 0) {
|
|
123
|
-
zeroReasonHints.push('No activity events match the specified filters');
|
|
124
|
-
zeroReasonHints.push('Note: Activity logs only show recent events');
|
|
125
|
-
if (args.eventType) {
|
|
126
|
-
zeroReasonHints.push(`Try removing the eventType filter (${args.eventType})`);
|
|
127
|
-
}
|
|
128
|
-
if (args.objectType) {
|
|
129
|
-
zeroReasonHints.push(`Try removing the objectType filter (${args.objectType})`);
|
|
130
|
-
}
|
|
131
|
-
if (args.objectId || args.projectId || args.taskId) {
|
|
132
|
-
zeroReasonHints.push('Verify the object ID is correct');
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
// Generate contextual next steps
|
|
136
|
-
const nextSteps = [];
|
|
137
|
-
if (events.length > 0) {
|
|
138
|
-
// Suggest related tools based on what was found
|
|
139
|
-
const hasTaskEvents = events.some((e) => e.objectType === 'task' || e.objectType === 'item');
|
|
140
|
-
const hasCompletions = events.some((e) => e.eventType === 'completed');
|
|
141
|
-
if (hasTaskEvents) {
|
|
142
|
-
nextSteps.push(`Use ${FIND_TASKS} to view current task details`);
|
|
143
|
-
}
|
|
144
|
-
if (hasCompletions) {
|
|
145
|
-
nextSteps.push('Review completed tasks to track productivity');
|
|
146
|
-
}
|
|
147
|
-
if (args.initiatorId) {
|
|
148
|
-
nextSteps.push(`Use ${USER_INFO} to get details about the user`);
|
|
149
|
-
}
|
|
150
|
-
// Suggest narrowing down if too many results
|
|
151
|
-
if (events.length >= args.limit && !nextCursor) {
|
|
152
|
-
nextSteps.push('Add more specific filters to narrow down results');
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return summarizeList({
|
|
156
|
-
subject,
|
|
157
|
-
count: events.length,
|
|
158
|
-
limit: args.limit,
|
|
159
|
-
nextCursor: nextCursor ?? undefined,
|
|
160
|
-
filterHints,
|
|
161
|
-
previewLines: previewActivityEvents(events, Math.min(events.length, args.limit)),
|
|
162
|
-
zeroReasonHints,
|
|
163
|
-
nextSteps,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Formats activity events into readable preview lines
|
|
168
|
-
*/
|
|
169
|
-
function previewActivityEvents(events, limit = 10) {
|
|
170
|
-
const previewEvents = events.slice(0, limit);
|
|
171
|
-
const lines = previewEvents.map(formatActivityEventPreview).join('\n');
|
|
172
|
-
// If we're showing fewer events than the total, add an indicator
|
|
173
|
-
if (events.length > limit) {
|
|
174
|
-
const remaining = events.length - limit;
|
|
175
|
-
return `${lines}\n ... and ${remaining} more event${remaining === 1 ? '' : 's'}`;
|
|
176
|
-
}
|
|
177
|
-
return lines;
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Formats a single activity event into a readable preview line
|
|
181
|
-
*/
|
|
182
|
-
function formatActivityEventPreview(event) {
|
|
183
|
-
const date = formatEventDate(event.eventDate);
|
|
184
|
-
const eventLabel = `${event.eventType} ${event.objectType}`;
|
|
185
|
-
// Extract useful content from extraData if available
|
|
186
|
-
let contentInfo = '';
|
|
187
|
-
if (event.extraData) {
|
|
188
|
-
const content = event.extraData.content || event.extraData.name || event.extraData.last_content;
|
|
189
|
-
if (content && typeof content === 'string') {
|
|
190
|
-
// Truncate long content
|
|
191
|
-
const truncated = content.length > 50 ? `${content.substring(0, 47)}...` : content;
|
|
192
|
-
contentInfo = ` • "${truncated}"`;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const objectId = event.objectId ? ` • id=${event.objectId}` : '';
|
|
196
|
-
const initiator = event.initiatorId ? ` • by=${event.initiatorId}` : ' • system';
|
|
197
|
-
const projectInfo = event.parentProjectId ? ` • project=${event.parentProjectId}` : '';
|
|
198
|
-
return ` [${date}] ${eventLabel}${contentInfo}${objectId}${initiator}${projectInfo}`;
|
|
199
|
-
}
|
|
200
|
-
/**
|
|
201
|
-
* Formats an ISO date string to a more readable format
|
|
202
|
-
*/
|
|
203
|
-
function formatEventDate(isoDate) {
|
|
204
|
-
try {
|
|
205
|
-
const date = new Date(isoDate);
|
|
206
|
-
// Format as: Oct 23, 14:30 (in UTC for deterministic snapshots)
|
|
207
|
-
const month = date.toLocaleDateString('en-US', { month: 'short', timeZone: 'UTC' });
|
|
208
|
-
const day = date.toLocaleDateString('en-US', { day: 'numeric', timeZone: 'UTC' });
|
|
209
|
-
const time = date.toLocaleTimeString('en-US', {
|
|
210
|
-
hour: '2-digit',
|
|
211
|
-
minute: '2-digit',
|
|
212
|
-
hour12: false,
|
|
213
|
-
timeZone: 'UTC',
|
|
214
|
-
});
|
|
215
|
-
return `${month} ${day}, ${time}`;
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
return isoDate;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
export { findActivity };
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
|
-
import { ApiLimits } from '../utils/constants.js';
|
|
4
|
-
import { formatNextSteps } from '../utils/response-builders.js';
|
|
5
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
6
|
-
const { ADD_COMMENTS, UPDATE_COMMENTS, DELETE_OBJECT } = ToolNames;
|
|
7
|
-
const ArgsSchema = {
|
|
8
|
-
taskId: z.string().optional().describe('Find comments for a specific task.'),
|
|
9
|
-
projectId: z.string().optional().describe('Find comments for a specific project.'),
|
|
10
|
-
commentId: z.string().optional().describe('Get a specific comment by ID.'),
|
|
11
|
-
cursor: z.string().optional().describe('Pagination cursor for retrieving more results.'),
|
|
12
|
-
limit: z
|
|
13
|
-
.number()
|
|
14
|
-
.int()
|
|
15
|
-
.min(1)
|
|
16
|
-
.max(ApiLimits.COMMENTS_MAX)
|
|
17
|
-
.optional()
|
|
18
|
-
.describe('Maximum number of comments to return'),
|
|
19
|
-
};
|
|
20
|
-
const findComments = {
|
|
21
|
-
name: ToolNames.FIND_COMMENTS,
|
|
22
|
-
description: 'Find comments by task, project, or get a specific comment by ID. Exactly one of taskId, projectId, or commentId must be provided.',
|
|
23
|
-
parameters: ArgsSchema,
|
|
24
|
-
async execute(args, client) {
|
|
25
|
-
// Validate that exactly one search parameter is provided
|
|
26
|
-
const searchParams = [args.taskId, args.projectId, args.commentId].filter(Boolean);
|
|
27
|
-
if (searchParams.length === 0) {
|
|
28
|
-
throw new Error('Must provide exactly one of: taskId, projectId, or commentId.');
|
|
29
|
-
}
|
|
30
|
-
if (searchParams.length > 1) {
|
|
31
|
-
throw new Error('Cannot provide multiple search parameters. Choose one of: taskId, projectId, or commentId.');
|
|
32
|
-
}
|
|
33
|
-
let comments;
|
|
34
|
-
let hasMore = false;
|
|
35
|
-
let nextCursor = null;
|
|
36
|
-
if (args.commentId) {
|
|
37
|
-
// Get single comment
|
|
38
|
-
const comment = await client.getComment(args.commentId);
|
|
39
|
-
comments = [comment];
|
|
40
|
-
}
|
|
41
|
-
else if (args.taskId) {
|
|
42
|
-
// Get comments by task
|
|
43
|
-
const response = await client.getComments({
|
|
44
|
-
taskId: args.taskId,
|
|
45
|
-
cursor: args.cursor || null,
|
|
46
|
-
limit: args.limit || ApiLimits.COMMENTS_DEFAULT,
|
|
47
|
-
});
|
|
48
|
-
comments = response.results;
|
|
49
|
-
hasMore = response.nextCursor !== null;
|
|
50
|
-
nextCursor = response.nextCursor;
|
|
51
|
-
}
|
|
52
|
-
else if (args.projectId) {
|
|
53
|
-
// Get comments by project
|
|
54
|
-
const response = await client.getComments({
|
|
55
|
-
projectId: args.projectId,
|
|
56
|
-
cursor: args.cursor || null,
|
|
57
|
-
limit: args.limit || ApiLimits.COMMENTS_DEFAULT,
|
|
58
|
-
});
|
|
59
|
-
comments = response.results;
|
|
60
|
-
hasMore = response.nextCursor !== null;
|
|
61
|
-
nextCursor = response.nextCursor;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
// This should never happen due to validation, but TypeScript needs it
|
|
65
|
-
throw new Error('Invalid state: no search parameter provided');
|
|
66
|
-
}
|
|
67
|
-
const textContent = generateTextContent({
|
|
68
|
-
comments,
|
|
69
|
-
searchType: args.commentId ? 'single' : args.taskId ? 'task' : 'project',
|
|
70
|
-
searchId: args.commentId || args.taskId || args.projectId || '',
|
|
71
|
-
hasMore,
|
|
72
|
-
nextCursor,
|
|
73
|
-
});
|
|
74
|
-
return getToolOutput({
|
|
75
|
-
textContent,
|
|
76
|
-
structuredContent: {
|
|
77
|
-
comments,
|
|
78
|
-
searchType: args.commentId ? 'single' : args.taskId ? 'task' : 'project',
|
|
79
|
-
searchId: args.commentId || args.taskId || args.projectId || '',
|
|
80
|
-
hasMore,
|
|
81
|
-
nextCursor,
|
|
82
|
-
totalCount: comments.length,
|
|
83
|
-
},
|
|
84
|
-
});
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
function generateTextContent({ comments, searchType, searchId, hasMore, nextCursor, }) {
|
|
88
|
-
if (comments.length === 0) {
|
|
89
|
-
return `No comments found for ${searchType}${searchType !== 'single' ? ` ${searchId}` : ''}`;
|
|
90
|
-
}
|
|
91
|
-
// Build summary
|
|
92
|
-
let summary;
|
|
93
|
-
if (searchType === 'single') {
|
|
94
|
-
const comment = comments[0];
|
|
95
|
-
if (!comment) {
|
|
96
|
-
return 'Comment not found';
|
|
97
|
-
}
|
|
98
|
-
const hasAttachment = comment.fileAttachment !== null;
|
|
99
|
-
const attachmentInfo = hasAttachment
|
|
100
|
-
? ` • Has attachment: ${comment.fileAttachment?.fileName || 'file'}`
|
|
101
|
-
: '';
|
|
102
|
-
summary = `Found comment${attachmentInfo} • id=${comment.id}`;
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
const attachmentCount = comments.filter((c) => c.fileAttachment !== null).length;
|
|
106
|
-
const attachmentInfo = attachmentCount > 0 ? ` (${attachmentCount} with attachments)` : '';
|
|
107
|
-
const commentsLabel = comments.length === 1 ? 'comment' : 'comments';
|
|
108
|
-
summary = `Found ${comments.length} ${commentsLabel} for ${searchType} ${searchId}${attachmentInfo}`;
|
|
109
|
-
if (hasMore) {
|
|
110
|
-
summary += ' • More available';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
// Context-aware next steps
|
|
114
|
-
const nextSteps = [];
|
|
115
|
-
if (searchType === 'single') {
|
|
116
|
-
const comment = comments[0];
|
|
117
|
-
if (comment) {
|
|
118
|
-
nextSteps.push(`Use ${UPDATE_COMMENTS} with id=${comment.id} to edit content`);
|
|
119
|
-
nextSteps.push(`Use ${DELETE_OBJECT} with type=comment id=${comment.id} to remove`);
|
|
120
|
-
// Suggest viewing related comments
|
|
121
|
-
if (comment.taskId) {
|
|
122
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with taskId=${comment.taskId} to see all task comments`);
|
|
123
|
-
}
|
|
124
|
-
else if (comment.projectId) {
|
|
125
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with projectId=${comment.projectId} to see all project comments`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
// Multiple comments
|
|
131
|
-
nextSteps.push(`Use ${ADD_COMMENTS} with ${searchType}Id=${searchId} to add new comment`);
|
|
132
|
-
if (comments.length > 0) {
|
|
133
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with commentId to view specific comment details`);
|
|
134
|
-
}
|
|
135
|
-
// Pagination
|
|
136
|
-
if (hasMore && nextCursor) {
|
|
137
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with cursor="${nextCursor}" to get more results`);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const next = formatNextSteps(nextSteps);
|
|
141
|
-
return `${summary}\n${next}`;
|
|
142
|
-
}
|
|
143
|
-
export { findComments };
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { appendToQuery, resolveResponsibleUser } from '../filter-helpers.js';
|
|
3
|
-
import { getToolOutput } from '../mcp-helpers.js';
|
|
4
|
-
import { mapTask } from '../tool-helpers.js';
|
|
5
|
-
import { ApiLimits } from '../utils/constants.js';
|
|
6
|
-
import { generateLabelsFilter, LabelsSchema } from '../utils/labels.js';
|
|
7
|
-
import { previewTasks, summarizeList } from '../utils/response-builders.js';
|
|
8
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
9
|
-
const { FIND_TASKS_BY_DATE, GET_OVERVIEW } = ToolNames;
|
|
10
|
-
const ArgsSchema = {
|
|
11
|
-
getBy: z
|
|
12
|
-
.enum(['completion', 'due'])
|
|
13
|
-
.default('completion')
|
|
14
|
-
.describe('The method to use to get the tasks: "completion" to get tasks by completion date (ie, when the task was actually completed), "due" to get tasks by due date (ie, when the task was due to be completed by).'),
|
|
15
|
-
since: z
|
|
16
|
-
.string()
|
|
17
|
-
.date()
|
|
18
|
-
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
19
|
-
.describe('The start date to get the tasks for. Format: YYYY-MM-DD.'),
|
|
20
|
-
until: z
|
|
21
|
-
.string()
|
|
22
|
-
.date()
|
|
23
|
-
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
24
|
-
.describe('The start date to get the tasks for. Format: YYYY-MM-DD.'),
|
|
25
|
-
workspaceId: z.string().optional().describe('The ID of the workspace to get the tasks for.'),
|
|
26
|
-
projectId: z.string().optional().describe('The ID of the project to get the tasks for.'),
|
|
27
|
-
sectionId: z.string().optional().describe('The ID of the section to get the tasks for.'),
|
|
28
|
-
parentId: z.string().optional().describe('The ID of the parent task to get the tasks for.'),
|
|
29
|
-
responsibleUser: z
|
|
30
|
-
.string()
|
|
31
|
-
.optional()
|
|
32
|
-
.describe('Find tasks assigned to this user. Can be a user ID, name, or email address. Defaults to all collaborators when omitted.'),
|
|
33
|
-
limit: z
|
|
34
|
-
.number()
|
|
35
|
-
.int()
|
|
36
|
-
.min(1)
|
|
37
|
-
.max(ApiLimits.COMPLETED_TASKS_MAX)
|
|
38
|
-
.default(ApiLimits.COMPLETED_TASKS_DEFAULT)
|
|
39
|
-
.describe('The maximum number of tasks to return.'),
|
|
40
|
-
cursor: z
|
|
41
|
-
.string()
|
|
42
|
-
.optional()
|
|
43
|
-
.describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
|
|
44
|
-
...LabelsSchema,
|
|
45
|
-
};
|
|
46
|
-
const findCompletedTasks = {
|
|
47
|
-
name: ToolNames.FIND_COMPLETED_TASKS,
|
|
48
|
-
description: 'Get completed tasks (includes all collaborators by default—use responsibleUser to narrow).',
|
|
49
|
-
parameters: ArgsSchema,
|
|
50
|
-
async execute(args, client) {
|
|
51
|
-
const { getBy, labels, labelsOperator, since, until, responsibleUser, ...rest } = args;
|
|
52
|
-
// Resolve assignee name to user ID if provided
|
|
53
|
-
const resolved = await resolveResponsibleUser(client, responsibleUser);
|
|
54
|
-
const assigneeEmail = resolved?.email;
|
|
55
|
-
// Build combined filter query (labels + assignment)
|
|
56
|
-
const labelsFilter = generateLabelsFilter(labels, labelsOperator);
|
|
57
|
-
let filterQuery = labelsFilter;
|
|
58
|
-
if (resolved && assigneeEmail) {
|
|
59
|
-
filterQuery = appendToQuery(filterQuery, `assigned to: ${assigneeEmail}`);
|
|
60
|
-
}
|
|
61
|
-
// Get user timezone to convert local dates to UTC
|
|
62
|
-
const user = await client.getUser();
|
|
63
|
-
const userGmtOffset = user.tzInfo?.gmtString || '+00:00';
|
|
64
|
-
// Convert user's local date to UTC timestamps
|
|
65
|
-
// This ensures we capture the entire day from the user's perspective
|
|
66
|
-
const sinceWithOffset = `${since}T00:00:00${userGmtOffset}`;
|
|
67
|
-
const untilWithOffset = `${until}T23:59:59${userGmtOffset}`;
|
|
68
|
-
// Parse and convert to UTC
|
|
69
|
-
const sinceDateTime = new Date(sinceWithOffset).toISOString();
|
|
70
|
-
const untilDateTime = new Date(untilWithOffset).toISOString();
|
|
71
|
-
const { items, nextCursor } = getBy === 'completion'
|
|
72
|
-
? await client.getCompletedTasksByCompletionDate({
|
|
73
|
-
...rest,
|
|
74
|
-
since: sinceDateTime,
|
|
75
|
-
until: untilDateTime,
|
|
76
|
-
...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
|
|
77
|
-
})
|
|
78
|
-
: await client.getCompletedTasksByDueDate({
|
|
79
|
-
...rest,
|
|
80
|
-
since: sinceDateTime,
|
|
81
|
-
until: untilDateTime,
|
|
82
|
-
...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
|
|
83
|
-
});
|
|
84
|
-
const mappedTasks = items.map(mapTask);
|
|
85
|
-
const textContent = generateTextContent({
|
|
86
|
-
tasks: mappedTasks,
|
|
87
|
-
args,
|
|
88
|
-
nextCursor,
|
|
89
|
-
assigneeEmail,
|
|
90
|
-
});
|
|
91
|
-
return getToolOutput({
|
|
92
|
-
textContent,
|
|
93
|
-
structuredContent: {
|
|
94
|
-
tasks: mappedTasks,
|
|
95
|
-
nextCursor,
|
|
96
|
-
totalCount: mappedTasks.length,
|
|
97
|
-
hasMore: Boolean(nextCursor),
|
|
98
|
-
appliedFilters: args,
|
|
99
|
-
},
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
};
|
|
103
|
-
function generateTextContent({ tasks, args, nextCursor, assigneeEmail, }) {
|
|
104
|
-
// Generate subject description
|
|
105
|
-
const getByText = args.getBy === 'completion' ? 'completed' : 'due';
|
|
106
|
-
const subject = `Completed tasks (by ${getByText} date)`;
|
|
107
|
-
// Generate filter hints
|
|
108
|
-
const filterHints = [];
|
|
109
|
-
filterHints.push(`${getByText} date: ${args.since} to ${args.until}`);
|
|
110
|
-
if (args.projectId)
|
|
111
|
-
filterHints.push(`project: ${args.projectId}`);
|
|
112
|
-
if (args.sectionId)
|
|
113
|
-
filterHints.push(`section: ${args.sectionId}`);
|
|
114
|
-
if (args.parentId)
|
|
115
|
-
filterHints.push(`parent: ${args.parentId}`);
|
|
116
|
-
if (args.workspaceId)
|
|
117
|
-
filterHints.push(`workspace: ${args.workspaceId}`);
|
|
118
|
-
// Add label filter information
|
|
119
|
-
if (args.labels && args.labels.length > 0) {
|
|
120
|
-
const labelText = args.labels
|
|
121
|
-
.map((label) => `@${label}`)
|
|
122
|
-
.join(args.labelsOperator === 'and' ? ' & ' : ' | ');
|
|
123
|
-
filterHints.push(`labels: ${labelText}`);
|
|
124
|
-
}
|
|
125
|
-
// Add responsible user filter information
|
|
126
|
-
if (args.responsibleUser) {
|
|
127
|
-
const email = assigneeEmail || args.responsibleUser;
|
|
128
|
-
filterHints.push(`assigned to: ${email}`);
|
|
129
|
-
}
|
|
130
|
-
// Generate helpful suggestions for empty results
|
|
131
|
-
const zeroReasonHints = [];
|
|
132
|
-
if (tasks.length === 0) {
|
|
133
|
-
zeroReasonHints.push('No tasks completed in this date range');
|
|
134
|
-
zeroReasonHints.push('Try expanding the date range');
|
|
135
|
-
if (args.projectId || args.sectionId || args.parentId) {
|
|
136
|
-
zeroReasonHints.push('Try removing project/section/parent filters');
|
|
137
|
-
}
|
|
138
|
-
if (args.getBy === 'due') {
|
|
139
|
-
zeroReasonHints.push('Try switching to "completion" date instead');
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
// Generate contextual next steps
|
|
143
|
-
const nextSteps = [];
|
|
144
|
-
if (tasks.length > 0) {
|
|
145
|
-
nextSteps.push(`Use ${FIND_TASKS_BY_DATE} for active tasks or ${GET_OVERVIEW} for current productivity.`);
|
|
146
|
-
if (tasks.some((task) => task.recurring)) {
|
|
147
|
-
nextSteps.push('Recurring tasks will automatically create new instances.');
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
return summarizeList({
|
|
151
|
-
subject,
|
|
152
|
-
count: tasks.length,
|
|
153
|
-
limit: args.limit,
|
|
154
|
-
nextCursor: nextCursor ?? undefined,
|
|
155
|
-
filterHints,
|
|
156
|
-
previewLines: previewTasks(tasks, Math.min(tasks.length, args.limit)),
|
|
157
|
-
zeroReasonHints,
|
|
158
|
-
nextSteps,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
export { findCompletedTasks };
|