motionmcp 2.2.4 → 2.4.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/README.md +121 -164
- package/dist/handlers/ProjectHandler.js +4 -4
- package/dist/handlers/ProjectHandler.js.map +1 -1
- package/dist/handlers/RecurringTaskHandler.js +2 -2
- package/dist/handlers/RecurringTaskHandler.js.map +1 -1
- package/dist/handlers/SearchHandler.d.ts.map +1 -1
- package/dist/handlers/SearchHandler.js +20 -4
- package/dist/handlers/SearchHandler.js.map +1 -1
- package/dist/handlers/TaskHandler.d.ts.map +1 -1
- package/dist/handlers/TaskHandler.js +31 -5
- package/dist/handlers/TaskHandler.js.map +1 -1
- package/dist/mcp-server-old.d.ts +29 -0
- package/dist/mcp-server-old.d.ts.map +1 -0
- package/dist/mcp-server-old.js +1304 -0
- package/dist/mcp-server-old.js.map +1 -0
- package/dist/mcp-server.js +0 -0
- package/dist/services/motionApi.d.ts +11 -9
- package/dist/services/motionApi.d.ts.map +1 -1
- package/dist/services/motionApi.js +182 -120
- package/dist/services/motionApi.js.map +1 -1
- package/dist/tools/ToolDefinitions.d.ts.map +1 -1
- package/dist/tools/ToolDefinitions.js +9 -2
- package/dist/tools/ToolDefinitions.js.map +1 -1
- package/dist/types/mcp-tool-args.d.ts +2 -1
- package/dist/types/mcp-tool-args.d.ts.map +1 -1
- package/dist/types/mcp.d.ts +10 -0
- package/dist/types/mcp.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/jsonSchemaToZod.d.ts +33 -0
- package/dist/utils/jsonSchemaToZod.d.ts.map +1 -0
- package/dist/utils/jsonSchemaToZod.js +110 -0
- package/dist/utils/jsonSchemaToZod.js.map +1 -0
- package/dist/utils/paginationNew.d.ts +2 -0
- package/dist/utils/paginationNew.d.ts.map +1 -1
- package/dist/utils/paginationNew.js +10 -1
- package/dist/utils/paginationNew.js.map +1 -1
- package/dist/utils/responseFormatters.d.ts +9 -1
- package/dist/utils/responseFormatters.d.ts.map +1 -1
- package/dist/utils/responseFormatters.js +33 -7
- package/dist/utils/responseFormatters.js.map +1 -1
- package/dist/worker.d.ts +17 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +61 -0
- package/dist/worker.js.map +1 -0
- package/package.json +6 -2
|
@@ -83,16 +83,16 @@ class MotionApiService {
|
|
|
83
83
|
throw error;
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
constructor() {
|
|
87
|
-
const
|
|
88
|
-
if (!
|
|
86
|
+
constructor(apiKey) {
|
|
87
|
+
const resolvedApiKey = apiKey || process.env.MOTION_API_KEY;
|
|
88
|
+
if (!resolvedApiKey) {
|
|
89
89
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Motion API key not found in environment variables', {
|
|
90
90
|
component: 'MotionApiService',
|
|
91
91
|
method: 'constructor'
|
|
92
92
|
});
|
|
93
93
|
throw new Error('MOTION_API_KEY environment variable is required');
|
|
94
94
|
}
|
|
95
|
-
this.apiKey =
|
|
95
|
+
this.apiKey = resolvedApiKey;
|
|
96
96
|
this.baseUrl = 'https://api.usemotion.com/v1';
|
|
97
97
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Initializing Motion API service', {
|
|
98
98
|
component: 'MotionApiService',
|
|
@@ -230,70 +230,77 @@ class MotionApiService {
|
|
|
230
230
|
throw new Error('limit must be a non-negative integer');
|
|
231
231
|
}
|
|
232
232
|
const cacheKey = `projects:workspace:${workspaceId}`;
|
|
233
|
-
return
|
|
233
|
+
// Check cache - return items only (no stale truncation info)
|
|
234
|
+
const cachedItems = this.projectCache.get(cacheKey);
|
|
235
|
+
if (cachedItems !== null) {
|
|
236
|
+
return { items: cachedItems };
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching projects from Motion API', {
|
|
240
|
+
method: 'getProjects',
|
|
241
|
+
workspaceId,
|
|
242
|
+
maxPages,
|
|
243
|
+
limit
|
|
244
|
+
});
|
|
245
|
+
// Create a fetch function for potential pagination
|
|
246
|
+
const fetchPage = async (cursor) => {
|
|
247
|
+
const params = new URLSearchParams();
|
|
248
|
+
params.append('workspaceId', workspaceId);
|
|
249
|
+
if (cursor) {
|
|
250
|
+
params.append('cursor', cursor);
|
|
251
|
+
}
|
|
252
|
+
const queryString = params.toString();
|
|
253
|
+
const url = `/projects?${queryString}`;
|
|
254
|
+
return this.requestWithRetry(() => this.client.get(url));
|
|
255
|
+
};
|
|
234
256
|
try {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
workspaceId,
|
|
257
|
+
// Attempt pagination-aware fetch with new response wrapper
|
|
258
|
+
const paginatedResult = await (0, paginationNew_1.fetchAllPages)(fetchPage, 'projects', {
|
|
238
259
|
maxPages,
|
|
239
|
-
|
|
260
|
+
logProgress: false,
|
|
261
|
+
...(limit ? { maxItems: limit } : {})
|
|
240
262
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
params.append('workspaceId', workspaceId);
|
|
245
|
-
if (cursor) {
|
|
246
|
-
params.append('cursor', cursor);
|
|
247
|
-
}
|
|
248
|
-
const queryString = params.toString();
|
|
249
|
-
const url = `/projects?${queryString}`;
|
|
250
|
-
return this.requestWithRetry(() => this.client.get(url));
|
|
251
|
-
};
|
|
252
|
-
try {
|
|
253
|
-
// Attempt pagination-aware fetch with new response wrapper
|
|
254
|
-
const paginatedResult = await (0, paginationNew_1.fetchAllPages)(fetchPage, 'projects', {
|
|
255
|
-
maxPages,
|
|
256
|
-
logProgress: false,
|
|
257
|
-
...(limit ? { maxItems: limit } : {})
|
|
258
|
-
});
|
|
259
|
-
if (paginatedResult.totalFetched > 0) {
|
|
260
|
-
let projects = paginatedResult.items;
|
|
261
|
-
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Projects fetched successfully with pagination', {
|
|
262
|
-
method: 'getProjects',
|
|
263
|
-
totalCount: projects.length,
|
|
264
|
-
hasMore: paginatedResult.hasMore,
|
|
265
|
-
workspaceId
|
|
266
|
-
});
|
|
267
|
-
return projects;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
catch (paginationError) {
|
|
271
|
-
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Pagination failed, falling back to simple fetch', {
|
|
263
|
+
if (paginatedResult.totalFetched > 0) {
|
|
264
|
+
let projects = paginatedResult.items;
|
|
265
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Projects fetched successfully with pagination', {
|
|
272
266
|
method: 'getProjects',
|
|
273
|
-
|
|
267
|
+
totalCount: projects.length,
|
|
268
|
+
hasMore: paginatedResult.hasMore,
|
|
269
|
+
workspaceId
|
|
274
270
|
});
|
|
271
|
+
// Cache only items, not truncation metadata
|
|
272
|
+
this.projectCache.set(cacheKey, projects);
|
|
273
|
+
return { items: projects, truncation: paginatedResult.truncation };
|
|
275
274
|
}
|
|
276
|
-
// Use new response wrapper for single page fallback
|
|
277
|
-
const response = await fetchPage();
|
|
278
|
-
const unwrapped = (0, responseWrapper_1.unwrapApiResponse)(response.data, 'projects');
|
|
279
|
-
let projects = unwrapped.data;
|
|
280
|
-
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Projects fetched successfully (single page)', {
|
|
281
|
-
method: 'getProjects',
|
|
282
|
-
count: projects.length,
|
|
283
|
-
workspaceId
|
|
284
|
-
});
|
|
285
|
-
return projects;
|
|
286
275
|
}
|
|
287
|
-
catch (
|
|
288
|
-
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.
|
|
276
|
+
catch (paginationError) {
|
|
277
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Pagination failed, falling back to simple fetch', {
|
|
289
278
|
method: 'getProjects',
|
|
290
|
-
error:
|
|
291
|
-
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
292
|
-
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
279
|
+
error: paginationError instanceof Error ? paginationError.message : String(paginationError)
|
|
293
280
|
});
|
|
294
|
-
throw this.formatApiError(error, 'fetch', 'project');
|
|
295
281
|
}
|
|
296
|
-
|
|
282
|
+
// Use new response wrapper for single page fallback
|
|
283
|
+
const response = await fetchPage();
|
|
284
|
+
const unwrapped = (0, responseWrapper_1.unwrapApiResponse)(response.data, 'projects');
|
|
285
|
+
let projects = unwrapped.data;
|
|
286
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Projects fetched successfully (single page)', {
|
|
287
|
+
method: 'getProjects',
|
|
288
|
+
count: projects.length,
|
|
289
|
+
workspaceId
|
|
290
|
+
});
|
|
291
|
+
// Cache only items
|
|
292
|
+
this.projectCache.set(cacheKey, projects);
|
|
293
|
+
return { items: projects };
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch projects', {
|
|
297
|
+
method: 'getProjects',
|
|
298
|
+
error: getErrorMessage(error),
|
|
299
|
+
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
300
|
+
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
301
|
+
});
|
|
302
|
+
throw this.formatApiError(error, 'fetch', 'project');
|
|
303
|
+
}
|
|
297
304
|
}
|
|
298
305
|
async getAllProjects() {
|
|
299
306
|
try {
|
|
@@ -302,10 +309,14 @@ class MotionApiService {
|
|
|
302
309
|
});
|
|
303
310
|
const allWorkspaces = await this.getWorkspaces();
|
|
304
311
|
const allProjects = [];
|
|
312
|
+
let aggregateTruncation;
|
|
305
313
|
for (const workspace of allWorkspaces) {
|
|
306
314
|
try {
|
|
307
|
-
const
|
|
308
|
-
allProjects.push(...
|
|
315
|
+
const { items, truncation } = await this.getProjects(workspace.id);
|
|
316
|
+
allProjects.push(...items);
|
|
317
|
+
if (truncation?.wasTruncated && !aggregateTruncation) {
|
|
318
|
+
aggregateTruncation = { ...truncation, returnedCount: allProjects.length };
|
|
319
|
+
}
|
|
309
320
|
}
|
|
310
321
|
catch (workspaceError) {
|
|
311
322
|
// Log error but continue with other workspaces
|
|
@@ -322,7 +333,10 @@ class MotionApiService {
|
|
|
322
333
|
totalProjects: allProjects.length,
|
|
323
334
|
workspaceCount: allWorkspaces.length
|
|
324
335
|
});
|
|
325
|
-
|
|
336
|
+
if (aggregateTruncation) {
|
|
337
|
+
aggregateTruncation.returnedCount = allProjects.length;
|
|
338
|
+
}
|
|
339
|
+
return { items: allProjects, truncation: aggregateTruncation };
|
|
326
340
|
}
|
|
327
341
|
catch (error) {
|
|
328
342
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch projects from all workspaces', {
|
|
@@ -457,7 +471,7 @@ class MotionApiService {
|
|
|
457
471
|
// TASK API METHODS
|
|
458
472
|
// ========================================
|
|
459
473
|
async getTasks(options) {
|
|
460
|
-
const { workspaceId, projectId, status, assigneeId, priority, dueDate, labels, limit, maxPages = 5 } = options;
|
|
474
|
+
const { workspaceId, projectId, status, includeAllStatuses, assigneeId, priority, dueDate, labels, limit, maxPages = 5 } = options;
|
|
461
475
|
// Validate limit parameter if provided
|
|
462
476
|
if (limit !== undefined && (limit < 0 || !Number.isInteger(limit))) {
|
|
463
477
|
throw new Error('limit must be a non-negative integer');
|
|
@@ -468,6 +482,7 @@ class MotionApiService {
|
|
|
468
482
|
workspaceId,
|
|
469
483
|
projectId,
|
|
470
484
|
status,
|
|
485
|
+
includeAllStatuses,
|
|
471
486
|
assigneeId,
|
|
472
487
|
priority,
|
|
473
488
|
dueDate,
|
|
@@ -482,7 +497,22 @@ class MotionApiService {
|
|
|
482
497
|
params.append('projectId', projectId);
|
|
483
498
|
}
|
|
484
499
|
if (status) {
|
|
485
|
-
|
|
500
|
+
if (Array.isArray(status)) {
|
|
501
|
+
// Deduplicate and skip empty strings before appending
|
|
502
|
+
// Note: Motion API supports repeated status= params for multi-value filtering
|
|
503
|
+
const uniqueStatuses = Array.from(new Set(status));
|
|
504
|
+
for (const s of uniqueStatuses) {
|
|
505
|
+
if (s) {
|
|
506
|
+
params.append('status', s);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
params.append('status', status);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (includeAllStatuses) {
|
|
515
|
+
params.append('includeAllStatuses', 'true');
|
|
486
516
|
}
|
|
487
517
|
if (assigneeId) {
|
|
488
518
|
params.append('assigneeId', assigneeId);
|
|
@@ -525,7 +555,7 @@ class MotionApiService {
|
|
|
525
555
|
projectId,
|
|
526
556
|
limitApplied: limit
|
|
527
557
|
});
|
|
528
|
-
return paginatedResult.items;
|
|
558
|
+
return { items: paginatedResult.items, truncation: paginatedResult.truncation };
|
|
529
559
|
}
|
|
530
560
|
}
|
|
531
561
|
catch (paginationError) {
|
|
@@ -550,7 +580,7 @@ class MotionApiService {
|
|
|
550
580
|
projectId,
|
|
551
581
|
limitApplied: limit
|
|
552
582
|
});
|
|
553
|
-
return tasks;
|
|
583
|
+
return { items: tasks };
|
|
554
584
|
}
|
|
555
585
|
catch (error) {
|
|
556
586
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch tasks', {
|
|
@@ -1010,7 +1040,7 @@ class MotionApiService {
|
|
|
1010
1040
|
workspaceId
|
|
1011
1041
|
});
|
|
1012
1042
|
// First, search in the specified workspace
|
|
1013
|
-
const projects = await this.getProjects(workspaceId);
|
|
1043
|
+
const { items: projects } = await this.getProjects(workspaceId);
|
|
1014
1044
|
const project = projects.find(p => p.name === projectName);
|
|
1015
1045
|
if (project) {
|
|
1016
1046
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Project found by name in specified workspace', {
|
|
@@ -1037,7 +1067,7 @@ class MotionApiService {
|
|
|
1037
1067
|
searchingWorkspaceId: workspace.id,
|
|
1038
1068
|
searchingWorkspaceName: workspace.name
|
|
1039
1069
|
});
|
|
1040
|
-
const workspaceProjects = await this.getProjects(workspace.id);
|
|
1070
|
+
const { items: workspaceProjects } = await this.getProjects(workspace.id);
|
|
1041
1071
|
const foundProject = workspaceProjects.find(p => p.name === projectName);
|
|
1042
1072
|
if (foundProject) {
|
|
1043
1073
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Project found by name in different workspace', {
|
|
@@ -1092,12 +1122,16 @@ class MotionApiService {
|
|
|
1092
1122
|
const effectiveLimit = limit || constants_1.LIMITS.MAX_SEARCH_RESULTS;
|
|
1093
1123
|
const lowerQuery = query.toLowerCase();
|
|
1094
1124
|
const allMatchingTasks = [];
|
|
1125
|
+
let aggregateTruncation;
|
|
1095
1126
|
// First, search in the specified workspace
|
|
1096
|
-
const primaryTasks = await this.getTasks({
|
|
1127
|
+
const { items: primaryTasks, truncation: primaryTruncation } = await this.getTasks({
|
|
1097
1128
|
workspaceId,
|
|
1098
1129
|
limit: (0, paginationNew_1.calculateAdaptiveFetchLimit)(allMatchingTasks.length, effectiveLimit),
|
|
1099
1130
|
maxPages: constants_1.LIMITS.MAX_PAGES
|
|
1100
1131
|
});
|
|
1132
|
+
if (primaryTruncation?.wasTruncated && !aggregateTruncation) {
|
|
1133
|
+
aggregateTruncation = primaryTruncation;
|
|
1134
|
+
}
|
|
1101
1135
|
const primaryMatches = primaryTasks.filter(task => task.name?.toLowerCase().includes(lowerQuery) ||
|
|
1102
1136
|
task.description?.toLowerCase().includes(lowerQuery));
|
|
1103
1137
|
allMatchingTasks.push(...primaryMatches.slice(0, effectiveLimit));
|
|
@@ -1128,11 +1162,14 @@ class MotionApiService {
|
|
|
1128
1162
|
searchingWorkspaceName: workspace.name,
|
|
1129
1163
|
remainingNeeded: effectiveLimit - allMatchingTasks.length
|
|
1130
1164
|
});
|
|
1131
|
-
const workspaceTasks = await this.getTasks({
|
|
1165
|
+
const { items: workspaceTasks, truncation: wsTruncation } = await this.getTasks({
|
|
1132
1166
|
workspaceId: workspace.id,
|
|
1133
1167
|
limit: fetchLimit,
|
|
1134
1168
|
maxPages: constants_1.LIMITS.MAX_PAGES
|
|
1135
1169
|
});
|
|
1170
|
+
if (wsTruncation?.wasTruncated && !aggregateTruncation) {
|
|
1171
|
+
aggregateTruncation = wsTruncation;
|
|
1172
|
+
}
|
|
1136
1173
|
const workspaceMatches = workspaceTasks.filter(task => task.name?.toLowerCase().includes(lowerQuery) ||
|
|
1137
1174
|
task.description?.toLowerCase().includes(lowerQuery));
|
|
1138
1175
|
// Only add as many as we still need
|
|
@@ -1176,7 +1213,10 @@ class MotionApiService {
|
|
|
1176
1213
|
returnedResults: allMatchingTasks.length,
|
|
1177
1214
|
limit: effectiveLimit
|
|
1178
1215
|
});
|
|
1179
|
-
|
|
1216
|
+
if (aggregateTruncation) {
|
|
1217
|
+
aggregateTruncation.returnedCount = allMatchingTasks.length;
|
|
1218
|
+
}
|
|
1219
|
+
return { items: allMatchingTasks, truncation: aggregateTruncation };
|
|
1180
1220
|
}
|
|
1181
1221
|
catch (error) {
|
|
1182
1222
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to search tasks', {
|
|
@@ -1199,11 +1239,15 @@ class MotionApiService {
|
|
|
1199
1239
|
const effectiveLimit = limit || constants_1.LIMITS.MAX_SEARCH_RESULTS;
|
|
1200
1240
|
const lowerQuery = query.toLowerCase();
|
|
1201
1241
|
const allMatchingProjects = [];
|
|
1242
|
+
let aggregateTruncation;
|
|
1202
1243
|
// First, search in the specified workspace
|
|
1203
|
-
const primaryProjects = await this.getProjects(workspaceId, {
|
|
1244
|
+
const { items: primaryProjects, truncation: primaryTruncation } = await this.getProjects(workspaceId, {
|
|
1204
1245
|
maxPages: constants_1.LIMITS.MAX_PAGES,
|
|
1205
1246
|
limit: (0, paginationNew_1.calculateAdaptiveFetchLimit)(allMatchingProjects.length, effectiveLimit)
|
|
1206
1247
|
});
|
|
1248
|
+
if (primaryTruncation?.wasTruncated && !aggregateTruncation) {
|
|
1249
|
+
aggregateTruncation = primaryTruncation;
|
|
1250
|
+
}
|
|
1207
1251
|
const primaryMatches = primaryProjects.filter(project => project.name?.toLowerCase().includes(lowerQuery) ||
|
|
1208
1252
|
project.description?.toLowerCase().includes(lowerQuery));
|
|
1209
1253
|
allMatchingProjects.push(...primaryMatches.slice(0, effectiveLimit));
|
|
@@ -1234,10 +1278,13 @@ class MotionApiService {
|
|
|
1234
1278
|
searchingWorkspaceName: workspace.name,
|
|
1235
1279
|
remainingNeeded: effectiveLimit - allMatchingProjects.length
|
|
1236
1280
|
});
|
|
1237
|
-
const workspaceProjects = await this.getProjects(workspace.id, {
|
|
1281
|
+
const { items: workspaceProjects, truncation: wsTruncation } = await this.getProjects(workspace.id, {
|
|
1238
1282
|
maxPages: constants_1.LIMITS.MAX_PAGES,
|
|
1239
1283
|
limit: fetchLimit
|
|
1240
1284
|
});
|
|
1285
|
+
if (wsTruncation?.wasTruncated && !aggregateTruncation) {
|
|
1286
|
+
aggregateTruncation = wsTruncation;
|
|
1287
|
+
}
|
|
1241
1288
|
const workspaceMatches = workspaceProjects.filter(project => project.name?.toLowerCase().includes(lowerQuery) ||
|
|
1242
1289
|
project.description?.toLowerCase().includes(lowerQuery));
|
|
1243
1290
|
// Only add as many as we still need
|
|
@@ -1281,7 +1328,10 @@ class MotionApiService {
|
|
|
1281
1328
|
returnedResults: allMatchingProjects.length,
|
|
1282
1329
|
limit: effectiveLimit
|
|
1283
1330
|
});
|
|
1284
|
-
|
|
1331
|
+
if (aggregateTruncation) {
|
|
1332
|
+
aggregateTruncation.returnedCount = allMatchingProjects.length;
|
|
1333
|
+
}
|
|
1334
|
+
return { items: allMatchingProjects, truncation: aggregateTruncation };
|
|
1285
1335
|
}
|
|
1286
1336
|
catch (error) {
|
|
1287
1337
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to search projects', {
|
|
@@ -1664,51 +1714,56 @@ class MotionApiService {
|
|
|
1664
1714
|
async getRecurringTasks(workspaceId, options) {
|
|
1665
1715
|
const { maxPages = 10, limit } = options || {};
|
|
1666
1716
|
const cacheKey = workspaceId ? `recurring-tasks:workspace:${workspaceId}` : 'recurring-tasks:all';
|
|
1667
|
-
return
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
const
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1717
|
+
// Check cache - return items only (no stale truncation info)
|
|
1718
|
+
const cachedItems = this.recurringTaskCache.get(cacheKey);
|
|
1719
|
+
if (cachedItems !== null) {
|
|
1720
|
+
return { items: cachedItems };
|
|
1721
|
+
}
|
|
1722
|
+
try {
|
|
1723
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching recurring tasks from Motion API with pagination', {
|
|
1724
|
+
method: 'getRecurringTasks',
|
|
1725
|
+
workspaceId,
|
|
1726
|
+
maxPages,
|
|
1727
|
+
limit
|
|
1728
|
+
});
|
|
1729
|
+
// Create a fetch function for pagination utility
|
|
1730
|
+
const fetchPage = async (cursor) => {
|
|
1731
|
+
const params = new URLSearchParams();
|
|
1732
|
+
if (workspaceId)
|
|
1733
|
+
params.append('workspaceId', workspaceId);
|
|
1734
|
+
if (cursor)
|
|
1735
|
+
params.append('cursor', cursor);
|
|
1736
|
+
const queryString = params.toString();
|
|
1737
|
+
const url = queryString ? `/recurring-tasks?${queryString}` : '/recurring-tasks';
|
|
1738
|
+
return this.requestWithRetry(() => this.client.get(url));
|
|
1739
|
+
};
|
|
1740
|
+
// Use pagination utility to fetch all pages
|
|
1741
|
+
const paginatedResult = await (0, paginationNew_1.fetchAllPages)(fetchPage, 'recurring-tasks', {
|
|
1742
|
+
maxPages,
|
|
1743
|
+
logProgress: true,
|
|
1744
|
+
...(limit ? { maxItems: limit } : {})
|
|
1745
|
+
});
|
|
1746
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Recurring tasks fetched successfully with pagination', {
|
|
1747
|
+
method: 'getRecurringTasks',
|
|
1748
|
+
totalCount: paginatedResult.totalFetched,
|
|
1749
|
+
pagesProcessed: Math.ceil(paginatedResult.totalFetched / 50), // Assuming ~50 items per page
|
|
1750
|
+
hasMore: paginatedResult.hasMore,
|
|
1751
|
+
workspaceId
|
|
1752
|
+
});
|
|
1753
|
+
// Cache only items, not truncation metadata
|
|
1754
|
+
this.recurringTaskCache.set(cacheKey, paginatedResult.items);
|
|
1755
|
+
return { items: paginatedResult.items, truncation: paginatedResult.truncation };
|
|
1756
|
+
}
|
|
1757
|
+
catch (error) {
|
|
1758
|
+
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch recurring tasks', {
|
|
1759
|
+
method: 'getRecurringTasks',
|
|
1760
|
+
error: getErrorMessage(error),
|
|
1761
|
+
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
1762
|
+
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1763
|
+
workspaceId
|
|
1764
|
+
});
|
|
1765
|
+
throw this.formatApiError(error, 'fetch', 'recurring task');
|
|
1766
|
+
}
|
|
1712
1767
|
}
|
|
1713
1768
|
/**
|
|
1714
1769
|
* Create a new recurring task
|
|
@@ -1953,6 +2008,7 @@ class MotionApiService {
|
|
|
1953
2008
|
// Apply limit to prevent resource exhaustion
|
|
1954
2009
|
const effectiveLimit = limit || constants_1.LIMITS.MAX_SEARCH_RESULTS;
|
|
1955
2010
|
const allUncompletedTasks = [];
|
|
2011
|
+
let aggregateTruncation;
|
|
1956
2012
|
try {
|
|
1957
2013
|
// Get all workspaces
|
|
1958
2014
|
const workspaces = await this.getWorkspaces();
|
|
@@ -1971,12 +2027,15 @@ class MotionApiService {
|
|
|
1971
2027
|
if (fetchLimit <= 0)
|
|
1972
2028
|
break;
|
|
1973
2029
|
// Get tasks from this workspace with adaptive limit
|
|
1974
|
-
const workspaceTasks = await this.getTasks({
|
|
2030
|
+
const { items: workspaceTasks, truncation: wsTruncation } = await this.getTasks({
|
|
1975
2031
|
workspaceId: workspace.id,
|
|
1976
2032
|
assigneeId,
|
|
1977
2033
|
limit: fetchLimit,
|
|
1978
2034
|
maxPages: constants_1.LIMITS.MAX_PAGES
|
|
1979
2035
|
});
|
|
2036
|
+
if (wsTruncation?.wasTruncated && !aggregateTruncation) {
|
|
2037
|
+
aggregateTruncation = wsTruncation;
|
|
2038
|
+
}
|
|
1980
2039
|
// Filter for uncompleted tasks
|
|
1981
2040
|
const uncompletedTasks = workspaceTasks.filter(task => {
|
|
1982
2041
|
// Task is uncompleted if status is missing or isResolvedStatus is false
|
|
@@ -2024,7 +2083,10 @@ class MotionApiService {
|
|
|
2024
2083
|
returned: allUncompletedTasks.length,
|
|
2025
2084
|
limit: effectiveLimit
|
|
2026
2085
|
});
|
|
2027
|
-
|
|
2086
|
+
if (aggregateTruncation) {
|
|
2087
|
+
aggregateTruncation.returnedCount = allUncompletedTasks.length;
|
|
2088
|
+
}
|
|
2089
|
+
return { items: allUncompletedTasks, truncation: aggregateTruncation };
|
|
2028
2090
|
}
|
|
2029
2091
|
catch (error) {
|
|
2030
2092
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch all uncompleted tasks', {
|