@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.
Files changed (178) hide show
  1. package/dist/filter-helpers.d.ts +1 -1
  2. package/dist/index.d.ts +1044 -196
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +61 -81
  5. package/dist/main.js +15 -23
  6. package/dist/mcp-helpers.d.ts +5 -5
  7. package/dist/mcp-helpers.d.ts.map +1 -1
  8. package/dist/mcp-server-BADReNAy.js +3092 -0
  9. package/dist/todoist-tool.d.ts +9 -3
  10. package/dist/todoist-tool.d.ts.map +1 -1
  11. package/dist/tool-helpers.d.ts +1 -1
  12. package/dist/tools/add-comments.d.ts +69 -3
  13. package/dist/tools/add-comments.d.ts.map +1 -1
  14. package/dist/tools/add-projects.d.ts +34 -3
  15. package/dist/tools/add-projects.d.ts.map +1 -1
  16. package/dist/tools/add-sections.d.ts +14 -1
  17. package/dist/tools/add-sections.d.ts.map +1 -1
  18. package/dist/tools/add-tasks.d.ts +65 -10
  19. package/dist/tools/add-tasks.d.ts.map +1 -1
  20. package/dist/tools/complete-tasks.d.ts +20 -1
  21. package/dist/tools/complete-tasks.d.ts.map +1 -1
  22. package/dist/tools/delete-object.d.ts +16 -3
  23. package/dist/tools/delete-object.d.ts.map +1 -1
  24. package/dist/tools/fetch.d.ts +8 -1
  25. package/dist/tools/fetch.d.ts.map +1 -1
  26. package/dist/tools/find-activity.d.ts +44 -7
  27. package/dist/tools/find-activity.d.ts.map +1 -1
  28. package/dist/tools/find-comments.d.ts +69 -3
  29. package/dist/tools/find-comments.d.ts.map +1 -1
  30. package/dist/tools/find-completed-tasks.d.ts +63 -5
  31. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  32. package/dist/tools/find-project-collaborators.d.ts +33 -2
  33. package/dist/tools/find-project-collaborators.d.ts.map +1 -1
  34. package/dist/tools/find-projects.d.ts +35 -1
  35. package/dist/tools/find-projects.d.ts.map +1 -1
  36. package/dist/tools/find-sections.d.ts +15 -1
  37. package/dist/tools/find-sections.d.ts.map +1 -1
  38. package/dist/tools/find-tasks-by-date.d.ts +61 -3
  39. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  40. package/dist/tools/find-tasks.d.ts +63 -5
  41. package/dist/tools/find-tasks.d.ts.map +1 -1
  42. package/dist/tools/get-overview.d.ts +24 -1
  43. package/dist/tools/get-overview.d.ts.map +1 -1
  44. package/dist/tools/manage-assignments.d.ts +39 -2
  45. package/dist/tools/manage-assignments.d.ts.map +1 -1
  46. package/dist/tools/search.d.ts +17 -1
  47. package/dist/tools/search.d.ts.map +1 -1
  48. package/dist/tools/update-comments.d.ts +76 -3
  49. package/dist/tools/update-comments.d.ts.map +1 -1
  50. package/dist/tools/update-projects.d.ts +43 -1
  51. package/dist/tools/update-projects.d.ts.map +1 -1
  52. package/dist/tools/update-sections.d.ts +17 -3
  53. package/dist/tools/update-sections.d.ts.map +1 -1
  54. package/dist/tools/update-tasks.d.ts +79 -13
  55. package/dist/tools/update-tasks.d.ts.map +1 -1
  56. package/dist/tools/user-info.d.ts +19 -1
  57. package/dist/tools/user-info.d.ts.map +1 -1
  58. package/dist/utils/assignment-validator.d.ts +2 -2
  59. package/dist/utils/output-schemas.d.ts +233 -0
  60. package/dist/utils/output-schemas.d.ts.map +1 -0
  61. package/dist/utils/response-builders.d.ts +1 -3
  62. package/dist/utils/response-builders.d.ts.map +1 -1
  63. package/dist/utils/test-helpers.d.ts +1 -1
  64. package/dist/utils/user-resolver.d.ts +1 -1
  65. package/package.json +10 -8
  66. package/dist/filter-helpers.js +0 -79
  67. package/dist/mcp-helpers.js +0 -71
  68. package/dist/mcp-server.js +0 -142
  69. package/dist/todoist-tool.js +0 -1
  70. package/dist/tool-helpers.js +0 -125
  71. package/dist/tool-helpers.test.d.ts +0 -2
  72. package/dist/tool-helpers.test.d.ts.map +0 -1
  73. package/dist/tool-helpers.test.js +0 -223
  74. package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
  75. package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
  76. package/dist/tools/__tests__/add-comments.test.js +0 -241
  77. package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
  78. package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
  79. package/dist/tools/__tests__/add-projects.test.js +0 -174
  80. package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
  81. package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
  82. package/dist/tools/__tests__/add-sections.test.js +0 -185
  83. package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
  84. package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
  85. package/dist/tools/__tests__/add-tasks.test.js +0 -606
  86. package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
  87. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
  88. package/dist/tools/__tests__/assignment-integration.test.js +0 -428
  89. package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
  90. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
  91. package/dist/tools/__tests__/complete-tasks.test.js +0 -206
  92. package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
  93. package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
  94. package/dist/tools/__tests__/delete-object.test.js +0 -110
  95. package/dist/tools/__tests__/fetch.test.d.ts +0 -2
  96. package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
  97. package/dist/tools/__tests__/fetch.test.js +0 -279
  98. package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
  99. package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
  100. package/dist/tools/__tests__/find-activity.test.js +0 -229
  101. package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
  102. package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
  103. package/dist/tools/__tests__/find-comments.test.js +0 -236
  104. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
  105. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
  106. package/dist/tools/__tests__/find-completed-tasks.test.js +0 -423
  107. package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
  108. package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
  109. package/dist/tools/__tests__/find-projects.test.js +0 -154
  110. package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
  111. package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
  112. package/dist/tools/__tests__/find-sections.test.js +0 -313
  113. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
  114. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
  115. package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
  116. package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
  117. package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
  118. package/dist/tools/__tests__/find-tasks.test.js +0 -771
  119. package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
  120. package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
  121. package/dist/tools/__tests__/get-overview.test.js +0 -225
  122. package/dist/tools/__tests__/search.test.d.ts +0 -2
  123. package/dist/tools/__tests__/search.test.d.ts.map +0 -1
  124. package/dist/tools/__tests__/search.test.js +0 -206
  125. package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
  126. package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
  127. package/dist/tools/__tests__/update-comments.test.js +0 -294
  128. package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
  129. package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
  130. package/dist/tools/__tests__/update-projects.test.js +0 -217
  131. package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
  132. package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
  133. package/dist/tools/__tests__/update-sections.test.js +0 -169
  134. package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
  135. package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
  136. package/dist/tools/__tests__/update-tasks.test.js +0 -788
  137. package/dist/tools/__tests__/user-info.test.d.ts +0 -2
  138. package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
  139. package/dist/tools/__tests__/user-info.test.js +0 -139
  140. package/dist/tools/add-comments.js +0 -89
  141. package/dist/tools/add-projects.js +0 -63
  142. package/dist/tools/add-sections.js +0 -74
  143. package/dist/tools/add-tasks.js +0 -169
  144. package/dist/tools/complete-tasks.js +0 -68
  145. package/dist/tools/delete-object.js +0 -79
  146. package/dist/tools/fetch.js +0 -102
  147. package/dist/tools/find-activity.js +0 -221
  148. package/dist/tools/find-comments.js +0 -148
  149. package/dist/tools/find-completed-tasks.js +0 -168
  150. package/dist/tools/find-project-collaborators.js +0 -151
  151. package/dist/tools/find-projects.js +0 -101
  152. package/dist/tools/find-sections.js +0 -101
  153. package/dist/tools/find-tasks-by-date.js +0 -198
  154. package/dist/tools/find-tasks.js +0 -329
  155. package/dist/tools/get-overview.js +0 -249
  156. package/dist/tools/manage-assignments.js +0 -337
  157. package/dist/tools/search.js +0 -65
  158. package/dist/tools/update-comments.js +0 -82
  159. package/dist/tools/update-projects.js +0 -84
  160. package/dist/tools/update-sections.js +0 -70
  161. package/dist/tools/update-tasks.js +0 -179
  162. package/dist/tools/user-info.js +0 -142
  163. package/dist/utils/assignment-validator.js +0 -253
  164. package/dist/utils/constants.js +0 -45
  165. package/dist/utils/duration-parser.js +0 -96
  166. package/dist/utils/duration-parser.test.d.ts +0 -2
  167. package/dist/utils/duration-parser.test.d.ts.map +0 -1
  168. package/dist/utils/duration-parser.test.js +0 -147
  169. package/dist/utils/labels.js +0 -18
  170. package/dist/utils/priorities.js +0 -20
  171. package/dist/utils/response-builders.js +0 -210
  172. package/dist/utils/sanitize-data.js +0 -37
  173. package/dist/utils/sanitize-data.test.d.ts +0 -2
  174. package/dist/utils/sanitize-data.test.d.ts.map +0 -1
  175. package/dist/utils/sanitize-data.test.js +0 -93
  176. package/dist/utils/test-helpers.js +0 -237
  177. package/dist/utils/tool-names.js +0 -40
  178. package/dist/utils/user-resolver.js +0 -179
@@ -1,168 +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
27
- .string()
28
- .optional()
29
- .describe('The ID of the project to get the tasks for. Project ID should be an ID string, or the text "inbox", for inbox tasks.'),
30
- sectionId: z.string().optional().describe('The ID of the section to get the tasks for.'),
31
- parentId: z.string().optional().describe('The ID of the parent task to get the tasks for.'),
32
- responsibleUser: z
33
- .string()
34
- .optional()
35
- .describe('Find tasks assigned to this user. Can be a user ID, name, or email address. Defaults to all collaborators when omitted.'),
36
- limit: z
37
- .number()
38
- .int()
39
- .min(1)
40
- .max(ApiLimits.COMPLETED_TASKS_MAX)
41
- .default(ApiLimits.COMPLETED_TASKS_DEFAULT)
42
- .describe('The maximum number of tasks to return.'),
43
- cursor: z
44
- .string()
45
- .optional()
46
- .describe('The cursor to get the next page of tasks (cursor is obtained from the previous call to this tool, with the same parameters).'),
47
- ...LabelsSchema,
48
- };
49
- const findCompletedTasks = {
50
- name: ToolNames.FIND_COMPLETED_TASKS,
51
- description: 'Get completed tasks (includes all collaborators by default—use responsibleUser to narrow).',
52
- parameters: ArgsSchema,
53
- async execute(args, client) {
54
- const { getBy, labels, labelsOperator, since, until, responsibleUser, projectId, ...rest } = args;
55
- // Resolve assignee name to user ID if provided
56
- const resolved = await resolveResponsibleUser(client, responsibleUser);
57
- const assigneeEmail = resolved?.email;
58
- // Build combined filter query (labels + assignment)
59
- const labelsFilter = generateLabelsFilter(labels, labelsOperator);
60
- let filterQuery = labelsFilter;
61
- if (resolved && assigneeEmail) {
62
- filterQuery = appendToQuery(filterQuery, `assigned to: ${assigneeEmail}`);
63
- }
64
- // Get user timezone to convert local dates to UTC
65
- const user = await client.getUser();
66
- const userGmtOffset = user.tzInfo?.gmtString || '+00:00';
67
- // Resolve "inbox" to actual inbox project ID if needed
68
- const resolvedProjectId = projectId === 'inbox' ? user.inboxProjectId : projectId;
69
- // Convert user's local date to UTC timestamps
70
- // This ensures we capture the entire day from the user's perspective
71
- const sinceWithOffset = `${since}T00:00:00${userGmtOffset}`;
72
- const untilWithOffset = `${until}T23:59:59${userGmtOffset}`;
73
- // Parse and convert to UTC
74
- const sinceDateTime = new Date(sinceWithOffset).toISOString();
75
- const untilDateTime = new Date(untilWithOffset).toISOString();
76
- const { items, nextCursor } = getBy === 'completion'
77
- ? await client.getCompletedTasksByCompletionDate({
78
- ...rest,
79
- projectId: resolvedProjectId,
80
- since: sinceDateTime,
81
- until: untilDateTime,
82
- ...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
83
- })
84
- : await client.getCompletedTasksByDueDate({
85
- ...rest,
86
- projectId: resolvedProjectId,
87
- since: sinceDateTime,
88
- until: untilDateTime,
89
- ...(filterQuery ? { filterQuery, filterLang: 'en' } : {}),
90
- });
91
- const mappedTasks = items.map(mapTask);
92
- const textContent = generateTextContent({
93
- tasks: mappedTasks,
94
- args,
95
- nextCursor,
96
- assigneeEmail,
97
- });
98
- return getToolOutput({
99
- textContent,
100
- structuredContent: {
101
- tasks: mappedTasks,
102
- nextCursor,
103
- totalCount: mappedTasks.length,
104
- hasMore: Boolean(nextCursor),
105
- appliedFilters: args,
106
- },
107
- });
108
- },
109
- };
110
- function generateTextContent({ tasks, args, nextCursor, assigneeEmail, }) {
111
- // Generate subject description
112
- const getByText = args.getBy === 'completion' ? 'completed' : 'due';
113
- const subject = `Completed tasks (by ${getByText} date)`;
114
- // Generate filter hints
115
- const filterHints = [];
116
- filterHints.push(`${getByText} date: ${args.since} to ${args.until}`);
117
- if (args.projectId)
118
- filterHints.push(`project: ${args.projectId}`);
119
- if (args.sectionId)
120
- filterHints.push(`section: ${args.sectionId}`);
121
- if (args.parentId)
122
- filterHints.push(`parent: ${args.parentId}`);
123
- if (args.workspaceId)
124
- filterHints.push(`workspace: ${args.workspaceId}`);
125
- // Add label filter information
126
- if (args.labels && args.labels.length > 0) {
127
- const labelText = args.labels
128
- .map((label) => `@${label}`)
129
- .join(args.labelsOperator === 'and' ? ' & ' : ' | ');
130
- filterHints.push(`labels: ${labelText}`);
131
- }
132
- // Add responsible user filter information
133
- if (args.responsibleUser) {
134
- const email = assigneeEmail || args.responsibleUser;
135
- filterHints.push(`assigned to: ${email}`);
136
- }
137
- // Generate helpful suggestions for empty results
138
- const zeroReasonHints = [];
139
- if (tasks.length === 0) {
140
- zeroReasonHints.push('No tasks completed in this date range');
141
- zeroReasonHints.push('Try expanding the date range');
142
- if (args.projectId || args.sectionId || args.parentId) {
143
- zeroReasonHints.push('Try removing project/section/parent filters');
144
- }
145
- if (args.getBy === 'due') {
146
- zeroReasonHints.push('Try switching to "completion" date instead');
147
- }
148
- }
149
- // Generate contextual next steps
150
- const nextSteps = [];
151
- if (tasks.length > 0) {
152
- nextSteps.push(`Use ${FIND_TASKS_BY_DATE} for active tasks or ${GET_OVERVIEW} for current productivity.`);
153
- if (tasks.some((task) => task.recurring)) {
154
- nextSteps.push('Recurring tasks will automatically create new instances.');
155
- }
156
- }
157
- return summarizeList({
158
- subject,
159
- count: tasks.length,
160
- limit: args.limit,
161
- nextCursor: nextCursor ?? undefined,
162
- filterHints,
163
- previewLines: previewTasks(tasks, Math.min(tasks.length, args.limit)),
164
- zeroReasonHints,
165
- nextSteps,
166
- });
167
- }
168
- export { findCompletedTasks };
@@ -1,151 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { summarizeList } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- import { userResolver } from '../utils/user-resolver.js';
6
- const { FIND_PROJECTS, ADD_TASKS, UPDATE_TASKS } = ToolNames;
7
- const ArgsSchema = {
8
- projectId: z.string().min(1).describe('The ID of the project to search for collaborators in.'),
9
- searchTerm: z
10
- .string()
11
- .optional()
12
- .describe('Search for a collaborator by name or email (partial and case insensitive match). If omitted, all collaborators in the project are returned.'),
13
- };
14
- const findProjectCollaborators = {
15
- name: ToolNames.FIND_PROJECT_COLLABORATORS,
16
- description: 'Search for collaborators by name or other criteria in a project.',
17
- parameters: ArgsSchema,
18
- async execute(args, client) {
19
- const { projectId, searchTerm } = args;
20
- // First, validate that the project exists and get basic info
21
- let projectName = projectId;
22
- let project;
23
- try {
24
- project = await client.getProject(projectId);
25
- if (!project) {
26
- throw new Error(`Project with ID "${projectId}" not found or not accessible`);
27
- }
28
- projectName = project.name;
29
- if (!project.isShared) {
30
- const textContent = `Project "${projectName}" is not shared and has no collaborators.\n\n**Next steps:**\n• Share the project to enable collaboration\n• Use ${ADD_TASKS} and ${UPDATE_TASKS} for assignment features once shared`;
31
- return getToolOutput({
32
- textContent,
33
- structuredContent: {
34
- collaborators: [],
35
- projectInfo: {
36
- id: projectId,
37
- name: projectName,
38
- isShared: false,
39
- },
40
- totalCount: 0,
41
- appliedFilters: args,
42
- },
43
- });
44
- }
45
- }
46
- catch (error) {
47
- throw new Error(`Failed to access project "${projectId}": ${error instanceof Error ? error.message : 'Unknown error'}`);
48
- }
49
- // Get collaborators for the project
50
- const allCollaborators = await userResolver.getProjectCollaborators(client, projectId);
51
- if (allCollaborators.length === 0) {
52
- const textContent = `Project "${projectName}" has no collaborators or collaborator data is not accessible.\n\n**Next steps:**\n• Check project sharing settings\n• Ensure you have permission to view collaborators\n• Try refreshing or re-sharing the project`;
53
- return getToolOutput({
54
- textContent,
55
- structuredContent: {
56
- collaborators: [],
57
- projectInfo: {
58
- id: projectId,
59
- name: projectName,
60
- isShared: true,
61
- },
62
- totalCount: 0,
63
- appliedFilters: args,
64
- },
65
- });
66
- }
67
- // Filter collaborators if search term provided
68
- let filteredCollaborators = allCollaborators;
69
- if (searchTerm) {
70
- const searchLower = searchTerm.toLowerCase().trim();
71
- filteredCollaborators = allCollaborators.filter((collaborator) => collaborator.name.toLowerCase().includes(searchLower) ||
72
- collaborator.email.toLowerCase().includes(searchLower));
73
- }
74
- const textContent = generateTextContent({
75
- collaborators: filteredCollaborators,
76
- projectName,
77
- searchTerm,
78
- totalAvailable: allCollaborators.length,
79
- });
80
- return getToolOutput({
81
- textContent,
82
- structuredContent: {
83
- collaborators: filteredCollaborators,
84
- projectInfo: {
85
- id: projectId,
86
- name: projectName,
87
- isShared: true,
88
- },
89
- totalCount: filteredCollaborators.length,
90
- totalAvailable: allCollaborators.length,
91
- appliedFilters: args,
92
- },
93
- });
94
- },
95
- };
96
- function generateTextContent({ collaborators, projectName, searchTerm, totalAvailable, }) {
97
- const subject = searchTerm
98
- ? `Project collaborators matching "${searchTerm}"`
99
- : 'Project collaborators';
100
- const filterHints = [];
101
- if (searchTerm) {
102
- filterHints.push(`matching "${searchTerm}"`);
103
- }
104
- filterHints.push(`in project "${projectName}"`);
105
- let previewLines = [];
106
- if (collaborators.length > 0) {
107
- previewLines = collaborators.slice(0, 10).map((collaborator) => {
108
- const displayName = collaborator.name || 'Unknown Name';
109
- const email = collaborator.email || 'No email';
110
- return `• ${displayName} (${email}) - ID: ${collaborator.id}`;
111
- });
112
- if (collaborators.length > 10) {
113
- previewLines.push(`... and ${collaborators.length - 10} more`);
114
- }
115
- }
116
- const zeroReasonHints = [];
117
- if (collaborators.length === 0) {
118
- if (searchTerm) {
119
- zeroReasonHints.push(`No collaborators match "${searchTerm}"`);
120
- zeroReasonHints.push('Try a broader search term or check spelling');
121
- if (totalAvailable > 0) {
122
- zeroReasonHints.push(`${totalAvailable} collaborators available without filter`);
123
- }
124
- }
125
- else {
126
- zeroReasonHints.push('Project has no collaborators');
127
- zeroReasonHints.push('Share the project to add collaborators');
128
- }
129
- }
130
- const nextSteps = [];
131
- if (collaborators.length > 0) {
132
- nextSteps.push(`Use ${ADD_TASKS} with responsibleUser to assign new tasks`);
133
- nextSteps.push(`Use ${UPDATE_TASKS} with responsibleUser to reassign existing tasks`);
134
- nextSteps.push('Use collaborator names, emails, or IDs for assignments');
135
- }
136
- else {
137
- nextSteps.push(`Use ${FIND_PROJECTS} to find other projects`);
138
- if (searchTerm && totalAvailable > 0) {
139
- nextSteps.push('Try searching without filters to see all collaborators');
140
- }
141
- }
142
- return summarizeList({
143
- subject,
144
- count: collaborators.length,
145
- filterHints,
146
- previewLines: previewLines.join('\n'),
147
- zeroReasonHints,
148
- nextSteps,
149
- });
150
- }
151
- export { findProjectCollaborators };
@@ -1,101 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { mapProject } from '../tool-helpers.js';
4
- import { ApiLimits } from '../utils/constants.js';
5
- import { formatProjectPreview, summarizeList } from '../utils/response-builders.js';
6
- import { ToolNames } from '../utils/tool-names.js';
7
- const { ADD_PROJECTS, FIND_TASKS } = ToolNames;
8
- const ArgsSchema = {
9
- search: z
10
- .string()
11
- .optional()
12
- .describe('Search for a project by name (partial and case insensitive match). If omitted, all projects are returned.'),
13
- limit: z
14
- .number()
15
- .int()
16
- .min(1)
17
- .max(ApiLimits.PROJECTS_MAX)
18
- .default(ApiLimits.PROJECTS_DEFAULT)
19
- .describe('The maximum number of projects to return.'),
20
- cursor: z
21
- .string()
22
- .optional()
23
- .describe('The cursor to get the next page of projects (cursor is obtained from the previous call to this tool, with the same parameters).'),
24
- };
25
- const findProjects = {
26
- name: ToolNames.FIND_PROJECTS,
27
- description: 'List all projects or search for projects by name. If search parameter is omitted, all projects are returned.',
28
- parameters: ArgsSchema,
29
- async execute(args, client) {
30
- const { results, nextCursor } = await client.getProjects({
31
- limit: args.limit,
32
- cursor: args.cursor ?? null,
33
- });
34
- const searchLower = args.search ? args.search.toLowerCase() : undefined;
35
- const filtered = searchLower
36
- ? results.filter((project) => project.name.toLowerCase().includes(searchLower))
37
- : results;
38
- const projects = filtered.map(mapProject);
39
- return getToolOutput({
40
- textContent: generateTextContent({
41
- projects,
42
- args,
43
- nextCursor,
44
- }),
45
- structuredContent: {
46
- projects,
47
- nextCursor,
48
- totalCount: projects.length,
49
- hasMore: Boolean(nextCursor),
50
- appliedFilters: args,
51
- },
52
- });
53
- },
54
- };
55
- function generateTextContent({ projects, args, nextCursor, }) {
56
- // Generate subject description
57
- const subject = args.search ? `Projects matching "${args.search}"` : 'Projects';
58
- // Generate filter hints
59
- const filterHints = [];
60
- if (args.search) {
61
- filterHints.push(`search: "${args.search}"`);
62
- }
63
- // Generate project preview lines
64
- const previewLimit = 10;
65
- const previewProjects = projects.slice(0, previewLimit);
66
- const previewLines = previewProjects.map(formatProjectPreview).join('\n');
67
- const remainingCount = projects.length - previewLimit;
68
- const previewWithMore = remainingCount > 0 ? `${previewLines}\n …and ${remainingCount} more` : previewLines;
69
- // Generate helpful suggestions for empty results
70
- const zeroReasonHints = [];
71
- if (projects.length === 0) {
72
- if (args.search) {
73
- zeroReasonHints.push('Try broader search terms');
74
- zeroReasonHints.push('Check spelling');
75
- zeroReasonHints.push('Remove search to see all projects');
76
- }
77
- else {
78
- zeroReasonHints.push('No projects created yet');
79
- zeroReasonHints.push(`Use ${ADD_PROJECTS} to create a project`);
80
- }
81
- }
82
- // Generate contextual next steps
83
- const nextSteps = [];
84
- if (projects.length > 0) {
85
- nextSteps.push(`Use ${FIND_TASKS} with projectId to see tasks in specific projects.`);
86
- if (projects.some((p) => p.isFavorite)) {
87
- nextSteps.push('Favorite projects appear first in most Todoist views.');
88
- }
89
- }
90
- return summarizeList({
91
- subject,
92
- count: projects.length,
93
- limit: args.limit,
94
- nextCursor: nextCursor ?? undefined,
95
- filterHints,
96
- previewLines: previewWithMore,
97
- zeroReasonHints,
98
- nextSteps,
99
- });
100
- }
101
- export { findProjects };
@@ -1,101 +0,0 @@
1
- import { z } from 'zod';
2
- import { getToolOutput } from '../mcp-helpers.js';
3
- import { summarizeList } from '../utils/response-builders.js';
4
- import { ToolNames } from '../utils/tool-names.js';
5
- const { ADD_SECTIONS, UPDATE_SECTIONS, FIND_TASKS, UPDATE_TASKS, DELETE_OBJECT } = ToolNames;
6
- const ArgsSchema = {
7
- projectId: z
8
- .string()
9
- .min(1)
10
- .describe('The ID of the project to search sections in. Project ID should be an ID string, or the text "inbox", for inbox tasks.'),
11
- search: z
12
- .string()
13
- .optional()
14
- .describe('Search for a section by name (partial and case insensitive match). If omitted, all sections in the project are returned.'),
15
- };
16
- const findSections = {
17
- name: ToolNames.FIND_SECTIONS,
18
- description: 'Search for sections by name or other criteria in a project.',
19
- parameters: ArgsSchema,
20
- async execute(args, client) {
21
- // Resolve "inbox" to actual inbox project ID if needed
22
- const resolvedProjectId = args.projectId === 'inbox' ? (await client.getUser()).inboxProjectId : args.projectId;
23
- const { results } = await client.getSections({
24
- projectId: resolvedProjectId,
25
- });
26
- const searchLower = args.search ? args.search.toLowerCase() : undefined;
27
- const filtered = searchLower
28
- ? results.filter((section) => section.name.toLowerCase().includes(searchLower))
29
- : results;
30
- const sections = filtered.map((section) => ({
31
- id: section.id,
32
- name: section.name,
33
- }));
34
- const textContent = generateTextContent({
35
- sections,
36
- projectId: args.projectId,
37
- search: args.search,
38
- });
39
- return getToolOutput({
40
- textContent,
41
- structuredContent: {
42
- sections,
43
- totalCount: sections.length,
44
- appliedFilters: args,
45
- },
46
- });
47
- },
48
- };
49
- function generateTextContent({ sections, projectId, search, }) {
50
- const zeroReasonHints = [];
51
- if (search) {
52
- zeroReasonHints.push('Try broader search terms');
53
- zeroReasonHints.push('Check spelling');
54
- zeroReasonHints.push('Remove search to see all sections');
55
- }
56
- else {
57
- zeroReasonHints.push('Project has no sections yet');
58
- zeroReasonHints.push(`Use ${ADD_SECTIONS} to create sections`);
59
- }
60
- // Data-driven next steps based on results
61
- const nextSteps = [];
62
- if (sections.length > 0) {
63
- // Suggestions based on number of sections found
64
- if (sections.length === 1) {
65
- const sectionId = sections[0]?.id;
66
- nextSteps.push(`Use ${FIND_TASKS} with sectionId=${sectionId} to see tasks`);
67
- nextSteps.push(`Use ${ADD_SECTIONS} to create additional sections for organization`);
68
- }
69
- else if (sections.length > 8) {
70
- nextSteps.push('Consider consolidating sections - many small sections can reduce productivity');
71
- nextSteps.push(`Use ${UPDATE_TASKS} to move tasks between sections`);
72
- nextSteps.push(`Use ${DELETE_OBJECT} with type=section to delete empty sections`);
73
- }
74
- else {
75
- nextSteps.push(`Use ${FIND_TASKS} with sectionId to see tasks in specific sections`);
76
- nextSteps.push(`Use ${UPDATE_SECTIONS} to modify section names`);
77
- }
78
- // Search-specific suggestions
79
- if (search) {
80
- nextSteps.push('Remove search parameter to see all sections in this project');
81
- }
82
- }
83
- else {
84
- // Empty result suggestions are already handled in zeroReasonHints
85
- // No additional nextSteps needed for empty results
86
- }
87
- const subject = search
88
- ? `Sections in project ${projectId} matching "${search}"`
89
- : `Sections in project ${projectId}`;
90
- const previewLines = sections.length > 0
91
- ? sections.map((section) => ` ${section.name} • id=${section.id}`).join('\n')
92
- : undefined;
93
- return summarizeList({
94
- subject,
95
- count: sections.length,
96
- previewLines,
97
- zeroReasonHints,
98
- nextSteps,
99
- });
100
- }
101
- export { findSections };