@doist/todoist-ai 4.16.0 → 4.17.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/filter-helpers.d.ts +1 -1
- package/dist/index.d.ts +1044 -196
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +61 -81
- package/dist/main.js +15 -23
- package/dist/mcp-helpers.d.ts +5 -5
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-server-BADReNAy.js +3092 -0
- package/dist/todoist-tool.d.ts +9 -3
- package/dist/todoist-tool.d.ts.map +1 -1
- package/dist/tool-helpers.d.ts +1 -1
- package/dist/tools/add-comments.d.ts +69 -3
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +34 -3
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +14 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +65 -10
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/complete-tasks.d.ts +20 -1
- package/dist/tools/complete-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +16 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/fetch.d.ts +8 -1
- package/dist/tools/fetch.d.ts.map +1 -1
- package/dist/tools/find-activity.d.ts +44 -7
- package/dist/tools/find-activity.d.ts.map +1 -1
- package/dist/tools/find-comments.d.ts +69 -3
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +63 -5
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-project-collaborators.d.ts +33 -2
- package/dist/tools/find-project-collaborators.d.ts.map +1 -1
- package/dist/tools/find-projects.d.ts +35 -1
- package/dist/tools/find-projects.d.ts.map +1 -1
- package/dist/tools/find-sections.d.ts +15 -1
- package/dist/tools/find-sections.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.d.ts +61 -3
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks.d.ts +63 -5
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/get-overview.d.ts +24 -1
- package/dist/tools/get-overview.d.ts.map +1 -1
- package/dist/tools/manage-assignments.d.ts +39 -2
- package/dist/tools/manage-assignments.d.ts.map +1 -1
- package/dist/tools/search.d.ts +17 -1
- package/dist/tools/search.d.ts.map +1 -1
- package/dist/tools/update-comments.d.ts +76 -3
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +43 -1
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +17 -3
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +79 -13
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/user-info.d.ts +19 -1
- package/dist/tools/user-info.d.ts.map +1 -1
- package/dist/utils/assignment-validator.d.ts +2 -2
- package/dist/utils/output-schemas.d.ts +233 -0
- package/dist/utils/output-schemas.d.ts.map +1 -0
- 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 +10 -8
- 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 -606
- 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 -423
- 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 -313
- 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 -89
- package/dist/tools/add-projects.js +0 -63
- package/dist/tools/add-sections.js +0 -74
- package/dist/tools/add-tasks.js +0 -169
- 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 -148
- package/dist/tools/find-completed-tasks.js +0 -168
- package/dist/tools/find-project-collaborators.js +0 -151
- package/dist/tools/find-projects.js +0 -101
- package/dist/tools/find-sections.js +0 -101
- 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 -179
- 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
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { getToolOutput } from '../mcp-helpers.js';
|
|
3
|
-
import { formatNextSteps } from '../utils/response-builders.js';
|
|
4
|
-
import { ToolNames } from '../utils/tool-names.js';
|
|
5
|
-
const { FIND_PROJECTS, GET_OVERVIEW, FIND_SECTIONS, FIND_TASKS, FIND_TASKS_BY_DATE, FIND_COMMENTS, } = ToolNames;
|
|
6
|
-
const ArgsSchema = {
|
|
7
|
-
type: z
|
|
8
|
-
.enum(['project', 'section', 'task', 'comment'])
|
|
9
|
-
.describe('The type of entity to delete.'),
|
|
10
|
-
id: z.string().min(1).describe('The ID of the entity to delete.'),
|
|
11
|
-
};
|
|
12
|
-
const deleteObject = {
|
|
13
|
-
name: ToolNames.DELETE_OBJECT,
|
|
14
|
-
description: 'Delete a project, section, task, or comment by its ID.',
|
|
15
|
-
parameters: ArgsSchema,
|
|
16
|
-
async execute(args, client) {
|
|
17
|
-
switch (args.type) {
|
|
18
|
-
case 'project':
|
|
19
|
-
await client.deleteProject(args.id);
|
|
20
|
-
break;
|
|
21
|
-
case 'section':
|
|
22
|
-
await client.deleteSection(args.id);
|
|
23
|
-
break;
|
|
24
|
-
case 'task':
|
|
25
|
-
await client.deleteTask(args.id);
|
|
26
|
-
break;
|
|
27
|
-
case 'comment':
|
|
28
|
-
await client.deleteComment(args.id);
|
|
29
|
-
break;
|
|
30
|
-
}
|
|
31
|
-
const textContent = generateTextContent({
|
|
32
|
-
type: args.type,
|
|
33
|
-
id: args.id,
|
|
34
|
-
});
|
|
35
|
-
return getToolOutput({
|
|
36
|
-
textContent,
|
|
37
|
-
structuredContent: {
|
|
38
|
-
deletedEntity: {
|
|
39
|
-
type: args.type,
|
|
40
|
-
id: args.id,
|
|
41
|
-
},
|
|
42
|
-
success: true,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
function generateTextContent({ type, id, }) {
|
|
48
|
-
const summary = `Deleted ${type}: id=${id}`;
|
|
49
|
-
// Recovery-focused next steps based on what was deleted
|
|
50
|
-
const nextSteps = [];
|
|
51
|
-
switch (type) {
|
|
52
|
-
case 'project':
|
|
53
|
-
// Help user understand impact and navigate remaining work
|
|
54
|
-
nextSteps.push(`Use ${FIND_PROJECTS} to see remaining projects`);
|
|
55
|
-
nextSteps.push('Note: All tasks and sections in this project were also deleted');
|
|
56
|
-
nextSteps.push(`Use ${GET_OVERVIEW} to review your updated project structure`);
|
|
57
|
-
break;
|
|
58
|
-
case 'section':
|
|
59
|
-
// Guide user to reorganize remaining sections and tasks
|
|
60
|
-
nextSteps.push(`Use ${FIND_SECTIONS} to see remaining sections in the project`);
|
|
61
|
-
nextSteps.push('Note: Tasks in this section were also deleted');
|
|
62
|
-
nextSteps.push(`Use ${FIND_TASKS} with projectId to see unorganized tasks`);
|
|
63
|
-
break;
|
|
64
|
-
case 'task':
|
|
65
|
-
// Help user stay focused on remaining work
|
|
66
|
-
nextSteps.push(`Use ${FIND_TASKS_BY_DATE} to see remaining tasks for today`);
|
|
67
|
-
nextSteps.push(`Use ${GET_OVERVIEW} to check if this affects any dependent tasks`);
|
|
68
|
-
nextSteps.push('Note: Any subtasks of this task were also deleted');
|
|
69
|
-
break;
|
|
70
|
-
case 'comment':
|
|
71
|
-
// Help user understand comment deletion impact
|
|
72
|
-
nextSteps.push(`Use ${FIND_COMMENTS} to see remaining comments on the task/project`);
|
|
73
|
-
nextSteps.push('Note: Comment attachments were also deleted');
|
|
74
|
-
break;
|
|
75
|
-
}
|
|
76
|
-
const next = formatNextSteps(nextSteps);
|
|
77
|
-
return `${summary}\n${next}`;
|
|
78
|
-
}
|
|
79
|
-
export { deleteObject };
|
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,148 +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
|
|
10
|
-
.string()
|
|
11
|
-
.optional()
|
|
12
|
-
.describe('Find comments for a specific project. Project ID should be an ID string, or the text "inbox", for inbox tasks.'),
|
|
13
|
-
commentId: z.string().optional().describe('Get a specific comment by ID.'),
|
|
14
|
-
cursor: z.string().optional().describe('Pagination cursor for retrieving more results.'),
|
|
15
|
-
limit: z
|
|
16
|
-
.number()
|
|
17
|
-
.int()
|
|
18
|
-
.min(1)
|
|
19
|
-
.max(ApiLimits.COMMENTS_MAX)
|
|
20
|
-
.optional()
|
|
21
|
-
.describe('Maximum number of comments to return'),
|
|
22
|
-
};
|
|
23
|
-
const findComments = {
|
|
24
|
-
name: ToolNames.FIND_COMMENTS,
|
|
25
|
-
description: 'Find comments by task, project, or get a specific comment by ID. Exactly one of taskId, projectId, or commentId must be provided.',
|
|
26
|
-
parameters: ArgsSchema,
|
|
27
|
-
async execute(args, client) {
|
|
28
|
-
// Validate that exactly one search parameter is provided
|
|
29
|
-
const searchParams = [args.taskId, args.projectId, args.commentId].filter(Boolean);
|
|
30
|
-
if (searchParams.length === 0) {
|
|
31
|
-
throw new Error('Must provide exactly one of: taskId, projectId, or commentId.');
|
|
32
|
-
}
|
|
33
|
-
if (searchParams.length > 1) {
|
|
34
|
-
throw new Error('Cannot provide multiple search parameters. Choose one of: taskId, projectId, or commentId.');
|
|
35
|
-
}
|
|
36
|
-
// Resolve "inbox" to actual inbox project ID if needed
|
|
37
|
-
const resolvedProjectId = args.projectId === 'inbox' ? (await client.getUser()).inboxProjectId : args.projectId;
|
|
38
|
-
let comments;
|
|
39
|
-
let hasMore = false;
|
|
40
|
-
let nextCursor = null;
|
|
41
|
-
if (args.commentId) {
|
|
42
|
-
// Get single comment
|
|
43
|
-
const comment = await client.getComment(args.commentId);
|
|
44
|
-
comments = [comment];
|
|
45
|
-
}
|
|
46
|
-
else if (args.taskId) {
|
|
47
|
-
// Get comments by task
|
|
48
|
-
const response = await client.getComments({
|
|
49
|
-
taskId: args.taskId,
|
|
50
|
-
cursor: args.cursor || null,
|
|
51
|
-
limit: args.limit || ApiLimits.COMMENTS_DEFAULT,
|
|
52
|
-
});
|
|
53
|
-
comments = response.results;
|
|
54
|
-
hasMore = response.nextCursor !== null;
|
|
55
|
-
nextCursor = response.nextCursor;
|
|
56
|
-
}
|
|
57
|
-
else if (resolvedProjectId) {
|
|
58
|
-
// Get comments by project
|
|
59
|
-
const response = await client.getComments({
|
|
60
|
-
projectId: resolvedProjectId,
|
|
61
|
-
cursor: args.cursor || null,
|
|
62
|
-
limit: args.limit || ApiLimits.COMMENTS_DEFAULT,
|
|
63
|
-
});
|
|
64
|
-
comments = response.results;
|
|
65
|
-
hasMore = response.nextCursor !== null;
|
|
66
|
-
nextCursor = response.nextCursor;
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
// This should never happen due to validation, but TypeScript needs it
|
|
70
|
-
throw new Error('Invalid state: no search parameter provided');
|
|
71
|
-
}
|
|
72
|
-
const textContent = generateTextContent({
|
|
73
|
-
comments,
|
|
74
|
-
searchType: args.commentId ? 'single' : args.taskId ? 'task' : 'project',
|
|
75
|
-
searchId: args.commentId || args.taskId || args.projectId || '',
|
|
76
|
-
hasMore,
|
|
77
|
-
nextCursor,
|
|
78
|
-
});
|
|
79
|
-
return getToolOutput({
|
|
80
|
-
textContent,
|
|
81
|
-
structuredContent: {
|
|
82
|
-
comments,
|
|
83
|
-
searchType: args.commentId ? 'single' : args.taskId ? 'task' : 'project',
|
|
84
|
-
searchId: args.commentId || args.taskId || args.projectId || '',
|
|
85
|
-
hasMore,
|
|
86
|
-
nextCursor,
|
|
87
|
-
totalCount: comments.length,
|
|
88
|
-
},
|
|
89
|
-
});
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
function generateTextContent({ comments, searchType, searchId, hasMore, nextCursor, }) {
|
|
93
|
-
if (comments.length === 0) {
|
|
94
|
-
return `No comments found for ${searchType}${searchType !== 'single' ? ` ${searchId}` : ''}`;
|
|
95
|
-
}
|
|
96
|
-
// Build summary
|
|
97
|
-
let summary;
|
|
98
|
-
if (searchType === 'single') {
|
|
99
|
-
const comment = comments[0];
|
|
100
|
-
if (!comment) {
|
|
101
|
-
return 'Comment not found';
|
|
102
|
-
}
|
|
103
|
-
const hasAttachment = comment.fileAttachment !== null;
|
|
104
|
-
const attachmentInfo = hasAttachment
|
|
105
|
-
? ` • Has attachment: ${comment.fileAttachment?.fileName || 'file'}`
|
|
106
|
-
: '';
|
|
107
|
-
summary = `Found comment${attachmentInfo} • id=${comment.id}`;
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
const attachmentCount = comments.filter((c) => c.fileAttachment !== null).length;
|
|
111
|
-
const attachmentInfo = attachmentCount > 0 ? ` (${attachmentCount} with attachments)` : '';
|
|
112
|
-
const commentsLabel = comments.length === 1 ? 'comment' : 'comments';
|
|
113
|
-
summary = `Found ${comments.length} ${commentsLabel} for ${searchType} ${searchId}${attachmentInfo}`;
|
|
114
|
-
if (hasMore) {
|
|
115
|
-
summary += ' • More available';
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
// Context-aware next steps
|
|
119
|
-
const nextSteps = [];
|
|
120
|
-
if (searchType === 'single') {
|
|
121
|
-
const comment = comments[0];
|
|
122
|
-
if (comment) {
|
|
123
|
-
nextSteps.push(`Use ${UPDATE_COMMENTS} with id=${comment.id} to edit content`);
|
|
124
|
-
nextSteps.push(`Use ${DELETE_OBJECT} with type=comment id=${comment.id} to remove`);
|
|
125
|
-
// Suggest viewing related comments
|
|
126
|
-
if (comment.taskId) {
|
|
127
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with taskId=${comment.taskId} to see all task comments`);
|
|
128
|
-
}
|
|
129
|
-
else if (comment.projectId) {
|
|
130
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with projectId=${comment.projectId} to see all project comments`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
// Multiple comments
|
|
136
|
-
nextSteps.push(`Use ${ADD_COMMENTS} with ${searchType}Id=${searchId} to add new comment`);
|
|
137
|
-
if (comments.length > 0) {
|
|
138
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with commentId to view specific comment details`);
|
|
139
|
-
}
|
|
140
|
-
// Pagination
|
|
141
|
-
if (hasMore && nextCursor) {
|
|
142
|
-
nextSteps.push(`Use ${ToolNames.FIND_COMMENTS} with cursor="${nextCursor}" to get more results`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const next = formatNextSteps(nextSteps);
|
|
146
|
-
return `${summary}\n${next}`;
|
|
147
|
-
}
|
|
148
|
-
export { findComments };
|