motionmcp 2.1.1 → 2.2.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/README.md +1 -1
- package/dist/handlers/CommentHandler.d.ts.map +1 -1
- package/dist/handlers/CommentHandler.js +3 -5
- package/dist/handlers/CommentHandler.js.map +1 -1
- package/dist/handlers/TaskHandler.d.ts +72 -0
- package/dist/handlers/TaskHandler.d.ts.map +1 -1
- package/dist/handlers/TaskHandler.js +77 -1
- package/dist/handlers/TaskHandler.js.map +1 -1
- package/dist/schemas/motion.d.ts +313 -4259
- package/dist/schemas/motion.d.ts.map +1 -1
- package/dist/schemas/motion.js +4 -4
- package/dist/schemas/motion.js.map +1 -1
- package/dist/services/motionApi.d.ts +15 -6
- package/dist/services/motionApi.d.ts.map +1 -1
- package/dist/services/motionApi.js +195 -108
- package/dist/services/motionApi.js.map +1 -1
- package/dist/tools/ToolDefinitions.d.ts.map +1 -1
- package/dist/tools/ToolDefinitions.js +23 -9
- package/dist/tools/ToolDefinitions.js.map +1 -1
- package/dist/tools/ToolRegistry.d.ts +50 -0
- package/dist/tools/ToolRegistry.d.ts.map +1 -1
- package/dist/tools/ToolRegistry.js +51 -6
- package/dist/tools/ToolRegistry.js.map +1 -1
- package/dist/types/mcp-tool-args.d.ts +2 -7
- package/dist/types/mcp-tool-args.d.ts.map +1 -1
- package/dist/types/motion.d.ts +11 -7
- package/dist/types/motion.d.ts.map +1 -1
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +6 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/errorHandling.d.ts +17 -2
- package/dist/utils/errorHandling.d.ts.map +1 -1
- package/dist/utils/errorHandling.js +46 -4
- package/dist/utils/errorHandling.js.map +1 -1
- package/dist/utils/frequencyTransform.d.ts +41 -0
- package/dist/utils/frequencyTransform.d.ts.map +1 -0
- package/dist/utils/frequencyTransform.js +326 -0
- package/dist/utils/frequencyTransform.js.map +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/pagination.d.ts +61 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +168 -0
- package/dist/utils/pagination.js.map +1 -0
- package/dist/utils/paginationNew.d.ts +14 -0
- package/dist/utils/paginationNew.d.ts.map +1 -1
- package/dist/utils/paginationNew.js +26 -0
- package/dist/utils/paginationNew.js.map +1 -1
- package/dist/utils/parameterUtils.d.ts.map +1 -1
- package/dist/utils/parameterUtils.js +11 -1
- package/dist/utils/parameterUtils.js.map +1 -1
- package/dist/utils/userFacingErrors.d.ts +49 -0
- package/dist/utils/userFacingErrors.d.ts.map +1 -0
- package/dist/utils/userFacingErrors.js +174 -0
- package/dist/utils/userFacingErrors.js.map +1 -0
- package/dist/utils/workspaceResolver.d.ts.map +1 -1
- package/dist/utils/workspaceResolver.js +6 -1
- package/dist/utils/workspaceResolver.js.map +1 -1
- package/package.json +15 -6
- package/dist/mcp-server-old.d.ts +0 -29
- package/dist/mcp-server-old.d.ts.map +0 -1
- package/dist/mcp-server-old.js +0 -1304
- package/dist/mcp-server-old.js.map +0 -1
|
@@ -36,10 +36,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.MotionApiService = void 0;
|
|
37
37
|
const axios_1 = __importStar(require("axios"));
|
|
38
38
|
const constants_1 = require("../utils/constants");
|
|
39
|
+
const frequencyTransform_1 = require("../utils/frequencyTransform");
|
|
39
40
|
const logger_1 = require("../utils/logger");
|
|
40
41
|
const cache_1 = require("../utils/cache");
|
|
41
42
|
const paginationNew_1 = require("../utils/paginationNew");
|
|
42
43
|
const responseWrapper_1 = require("../utils/responseWrapper");
|
|
44
|
+
const userFacingErrors_1 = require("../utils/userFacingErrors");
|
|
43
45
|
const zod_1 = require("zod");
|
|
44
46
|
const motion_1 = require("../schemas/motion");
|
|
45
47
|
// Note: Using native axios.isAxiosError instead of custom implementation
|
|
@@ -66,7 +68,7 @@ class MotionApiService {
|
|
|
66
68
|
if (error instanceof zod_1.z.ZodError) {
|
|
67
69
|
const errorDetails = {
|
|
68
70
|
context,
|
|
69
|
-
validationErrors: error.
|
|
71
|
+
validationErrors: error.issues,
|
|
70
72
|
...(motion_1.VALIDATION_CONFIG.includeDataInLogs ? { receivedData: data } : {})
|
|
71
73
|
};
|
|
72
74
|
if (motion_1.VALIDATION_CONFIG.logErrors) {
|
|
@@ -101,7 +103,8 @@ class MotionApiService {
|
|
|
101
103
|
headers: {
|
|
102
104
|
'X-API-Key': this.apiKey,
|
|
103
105
|
'Content-Type': 'application/json'
|
|
104
|
-
}
|
|
106
|
+
},
|
|
107
|
+
timeout: constants_1.API_CONFIG.TIMEOUT_MS
|
|
105
108
|
});
|
|
106
109
|
// Initialize cache instances with TTL from constants (converted to ms)
|
|
107
110
|
this.workspaceCache = new cache_1.SimpleCache(constants_1.CACHE_TTL.WORKSPACES);
|
|
@@ -135,23 +138,32 @@ class MotionApiService {
|
|
|
135
138
|
};
|
|
136
139
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Motion API request failed', errorDetails);
|
|
137
140
|
// Create typed error for better handling
|
|
138
|
-
const typedError = new Error(errorData?.message || error.message)
|
|
139
|
-
|
|
141
|
+
const typedError = Object.assign(new Error(errorData?.message || error.message), {
|
|
142
|
+
response: error.response ? {
|
|
143
|
+
status: error.response.status,
|
|
144
|
+
statusText: error.response.statusText,
|
|
145
|
+
data: error.response.data
|
|
146
|
+
} : undefined
|
|
147
|
+
});
|
|
140
148
|
throw typedError;
|
|
141
149
|
});
|
|
142
150
|
}
|
|
143
151
|
/**
|
|
144
|
-
* Formats API errors
|
|
152
|
+
* Formats API errors with user-friendly messages while preserving technical details
|
|
145
153
|
* @param error - The error that occurred
|
|
146
|
-
* @param action - Description of the action that failed (e.g., 'fetch
|
|
147
|
-
* @
|
|
154
|
+
* @param action - Description of the action that failed (e.g., 'fetch', 'create', 'update')
|
|
155
|
+
* @param resourceType - Type of resource being operated on (e.g., 'project', 'task')
|
|
156
|
+
* @param resourceId - Optional ID of the resource
|
|
157
|
+
* @param resourceName - Optional name of the resource
|
|
158
|
+
* @returns UserFacingError with both user-friendly and technical messages
|
|
148
159
|
*/
|
|
149
|
-
formatApiError(error, action) {
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
const errorMessage = getErrorMessage(error);
|
|
153
|
-
return new Error(`${baseMessage}: ${apiMessage || errorMessage}`);
|
|
160
|
+
formatApiError(error, action, resourceType, resourceId, resourceName) {
|
|
161
|
+
const context = (0, userFacingErrors_1.createErrorContext)(action, resourceType, resourceId, resourceName);
|
|
162
|
+
return (0, userFacingErrors_1.createUserFacingError)(error, context);
|
|
154
163
|
}
|
|
164
|
+
// ========================================
|
|
165
|
+
// PRIVATE HELPER METHODS
|
|
166
|
+
// ========================================
|
|
155
167
|
/**
|
|
156
168
|
* Wraps an axios request with a retry mechanism featuring exponential backoff.
|
|
157
169
|
* Only retries on 5xx server errors or 429 rate-limiting errors.
|
|
@@ -208,14 +220,23 @@ class MotionApiService {
|
|
|
208
220
|
// Should never reach here, but TypeScript requires a return or throw
|
|
209
221
|
throw new Error('Max retries exceeded');
|
|
210
222
|
}
|
|
211
|
-
|
|
223
|
+
// ========================================
|
|
224
|
+
// PROJECT API METHODS
|
|
225
|
+
// ========================================
|
|
226
|
+
async getProjects(workspaceId, options) {
|
|
227
|
+
const { maxPages = 5, limit } = options || {};
|
|
228
|
+
// Validate limit parameter if provided
|
|
229
|
+
if (limit !== undefined && (limit < 0 || !Number.isInteger(limit))) {
|
|
230
|
+
throw new Error('limit must be a non-negative integer');
|
|
231
|
+
}
|
|
212
232
|
const cacheKey = `projects:workspace:${workspaceId}`;
|
|
213
233
|
return this.projectCache.withCache(cacheKey, async () => {
|
|
214
234
|
try {
|
|
215
235
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching projects from Motion API', {
|
|
216
236
|
method: 'getProjects',
|
|
217
237
|
workspaceId,
|
|
218
|
-
maxPages
|
|
238
|
+
maxPages,
|
|
239
|
+
limit
|
|
219
240
|
});
|
|
220
241
|
// Create a fetch function for potential pagination
|
|
221
242
|
const fetchPage = async (cursor) => {
|
|
@@ -232,7 +253,8 @@ class MotionApiService {
|
|
|
232
253
|
// Attempt pagination-aware fetch with new response wrapper
|
|
233
254
|
const paginatedResult = await (0, paginationNew_1.fetchAllPages)(fetchPage, 'projects', {
|
|
234
255
|
maxPages,
|
|
235
|
-
logProgress: false
|
|
256
|
+
logProgress: false,
|
|
257
|
+
...(limit ? { maxItems: limit } : {})
|
|
236
258
|
});
|
|
237
259
|
if (paginatedResult.totalFetched > 0) {
|
|
238
260
|
let projects = paginatedResult.items;
|
|
@@ -269,7 +291,7 @@ class MotionApiService {
|
|
|
269
291
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
270
292
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
271
293
|
});
|
|
272
|
-
throw this.formatApiError(error, 'fetch
|
|
294
|
+
throw this.formatApiError(error, 'fetch', 'project');
|
|
273
295
|
}
|
|
274
296
|
});
|
|
275
297
|
}
|
|
@@ -307,7 +329,7 @@ class MotionApiService {
|
|
|
307
329
|
method: 'getAllProjects',
|
|
308
330
|
error: getErrorMessage(error)
|
|
309
331
|
});
|
|
310
|
-
throw this.formatApiError(error, 'fetch
|
|
332
|
+
throw this.formatApiError(error, 'fetch', 'project');
|
|
311
333
|
}
|
|
312
334
|
}
|
|
313
335
|
async getProject(projectId) {
|
|
@@ -334,7 +356,7 @@ class MotionApiService {
|
|
|
334
356
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
335
357
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
336
358
|
});
|
|
337
|
-
throw this.formatApiError(error, 'fetch project');
|
|
359
|
+
throw this.formatApiError(error, 'fetch', 'project', projectId);
|
|
338
360
|
}
|
|
339
361
|
});
|
|
340
362
|
}
|
|
@@ -373,7 +395,7 @@ class MotionApiService {
|
|
|
373
395
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
374
396
|
fullErrorResponse: (0, axios_1.isAxiosError)(error) ? JSON.stringify(error.response?.data, null, 2) : undefined
|
|
375
397
|
});
|
|
376
|
-
throw this.formatApiError(error, 'create project');
|
|
398
|
+
throw this.formatApiError(error, 'create', 'project', undefined, projectData.name);
|
|
377
399
|
}
|
|
378
400
|
}
|
|
379
401
|
async updateProject(projectId, updates) {
|
|
@@ -403,7 +425,7 @@ class MotionApiService {
|
|
|
403
425
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
404
426
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
405
427
|
});
|
|
406
|
-
throw this.formatApiError(error, 'update project');
|
|
428
|
+
throw this.formatApiError(error, 'update', 'project', projectId);
|
|
407
429
|
}
|
|
408
430
|
}
|
|
409
431
|
async deleteProject(projectId) {
|
|
@@ -428,11 +450,18 @@ class MotionApiService {
|
|
|
428
450
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
429
451
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
430
452
|
});
|
|
431
|
-
throw this.formatApiError(error, 'delete project');
|
|
453
|
+
throw this.formatApiError(error, 'delete', 'project', projectId);
|
|
432
454
|
}
|
|
433
455
|
}
|
|
456
|
+
// ========================================
|
|
457
|
+
// TASK API METHODS
|
|
458
|
+
// ========================================
|
|
434
459
|
async getTasks(options) {
|
|
435
460
|
const { workspaceId, projectId, status, assigneeId, priority, dueDate, labels, limit, maxPages = 5 } = options;
|
|
461
|
+
// Validate limit parameter if provided
|
|
462
|
+
if (limit !== undefined && (limit < 0 || !Number.isInteger(limit))) {
|
|
463
|
+
throw new Error('limit must be a non-negative integer');
|
|
464
|
+
}
|
|
436
465
|
try {
|
|
437
466
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching tasks from Motion API', {
|
|
438
467
|
method: 'getTasks',
|
|
@@ -486,21 +515,17 @@ class MotionApiService {
|
|
|
486
515
|
...(limit ? { maxItems: limit } : {})
|
|
487
516
|
});
|
|
488
517
|
if (paginatedResult.totalFetched > 0) {
|
|
489
|
-
|
|
490
|
-
// Apply limit if specified
|
|
491
|
-
if (limit && limit > 0) {
|
|
492
|
-
tasks = tasks.slice(0, limit);
|
|
493
|
-
}
|
|
518
|
+
// Note: limit is already enforced by maxItems in pagination, no need to slice
|
|
494
519
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Tasks fetched successfully with pagination', {
|
|
495
520
|
method: 'getTasks',
|
|
496
521
|
totalCount: paginatedResult.totalFetched,
|
|
497
|
-
returnedCount:
|
|
522
|
+
returnedCount: paginatedResult.items.length,
|
|
498
523
|
hasMore: paginatedResult.hasMore,
|
|
499
524
|
workspaceId,
|
|
500
525
|
projectId,
|
|
501
526
|
limitApplied: limit
|
|
502
527
|
});
|
|
503
|
-
return
|
|
528
|
+
return paginatedResult.items;
|
|
504
529
|
}
|
|
505
530
|
}
|
|
506
531
|
catch (paginationError) {
|
|
@@ -534,7 +559,7 @@ class MotionApiService {
|
|
|
534
559
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
535
560
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
536
561
|
});
|
|
537
|
-
throw this.formatApiError(error, 'fetch
|
|
562
|
+
throw this.formatApiError(error, 'fetch', 'task');
|
|
538
563
|
}
|
|
539
564
|
}
|
|
540
565
|
async getTask(taskId) {
|
|
@@ -559,7 +584,7 @@ class MotionApiService {
|
|
|
559
584
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
560
585
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
561
586
|
});
|
|
562
|
-
throw this.formatApiError(error, 'fetch task');
|
|
587
|
+
throw this.formatApiError(error, 'fetch', 'task', taskId);
|
|
563
588
|
}
|
|
564
589
|
}
|
|
565
590
|
async createTask(taskData) {
|
|
@@ -598,7 +623,7 @@ class MotionApiService {
|
|
|
598
623
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
599
624
|
fullErrorResponse: (0, axios_1.isAxiosError)(error) ? JSON.stringify(error.response?.data, null, 2) : undefined
|
|
600
625
|
});
|
|
601
|
-
throw this.formatApiError(error, 'create task');
|
|
626
|
+
throw this.formatApiError(error, 'create', 'task', undefined, taskData.name);
|
|
602
627
|
}
|
|
603
628
|
}
|
|
604
629
|
async updateTask(taskId, updates) {
|
|
@@ -626,7 +651,7 @@ class MotionApiService {
|
|
|
626
651
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
627
652
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
628
653
|
});
|
|
629
|
-
throw this.formatApiError(error, 'update task');
|
|
654
|
+
throw this.formatApiError(error, 'update', 'task', taskId);
|
|
630
655
|
}
|
|
631
656
|
}
|
|
632
657
|
async deleteTask(taskId) {
|
|
@@ -649,7 +674,7 @@ class MotionApiService {
|
|
|
649
674
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
650
675
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
651
676
|
});
|
|
652
|
-
throw this.formatApiError(error, 'delete task');
|
|
677
|
+
throw this.formatApiError(error, 'delete', 'task', taskId);
|
|
653
678
|
}
|
|
654
679
|
}
|
|
655
680
|
async moveTask(taskId, targetProjectId, targetWorkspaceId) {
|
|
@@ -675,7 +700,7 @@ class MotionApiService {
|
|
|
675
700
|
targetProjectId,
|
|
676
701
|
targetWorkspaceId
|
|
677
702
|
});
|
|
678
|
-
//
|
|
703
|
+
// Note: No task cache currently implemented - tasks are not cached due to frequent updates
|
|
679
704
|
return response.data;
|
|
680
705
|
}
|
|
681
706
|
catch (error) {
|
|
@@ -688,7 +713,7 @@ class MotionApiService {
|
|
|
688
713
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
689
714
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
690
715
|
});
|
|
691
|
-
throw this.formatApiError(error, 'move task');
|
|
716
|
+
throw this.formatApiError(error, 'move', 'task', taskId);
|
|
692
717
|
}
|
|
693
718
|
}
|
|
694
719
|
async unassignTask(taskId) {
|
|
@@ -702,7 +727,7 @@ class MotionApiService {
|
|
|
702
727
|
method: 'unassignTask',
|
|
703
728
|
taskId
|
|
704
729
|
});
|
|
705
|
-
//
|
|
730
|
+
// Note: No task cache currently implemented - tasks are not cached due to frequent updates
|
|
706
731
|
return response.data;
|
|
707
732
|
}
|
|
708
733
|
catch (error) {
|
|
@@ -713,9 +738,12 @@ class MotionApiService {
|
|
|
713
738
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
714
739
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
715
740
|
});
|
|
716
|
-
throw this.formatApiError(error, 'unassign task');
|
|
741
|
+
throw this.formatApiError(error, 'unassign', 'task', taskId);
|
|
717
742
|
}
|
|
718
743
|
}
|
|
744
|
+
// ========================================
|
|
745
|
+
// WORKSPACE API METHODS
|
|
746
|
+
// ========================================
|
|
719
747
|
async getWorkspaces() {
|
|
720
748
|
return this.workspaceCache.withCache('workspaces', async () => {
|
|
721
749
|
try {
|
|
@@ -743,10 +771,13 @@ class MotionApiService {
|
|
|
743
771
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
744
772
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
745
773
|
});
|
|
746
|
-
throw this.formatApiError(error, 'fetch
|
|
774
|
+
throw this.formatApiError(error, 'fetch', 'workspace');
|
|
747
775
|
}
|
|
748
776
|
});
|
|
749
777
|
}
|
|
778
|
+
// ========================================
|
|
779
|
+
// USER API METHODS
|
|
780
|
+
// ========================================
|
|
750
781
|
async getUsers(workspaceId) {
|
|
751
782
|
const cacheKey = workspaceId ? `users:workspace:${workspaceId}` : 'users:all';
|
|
752
783
|
return this.userCache.withCache(cacheKey, async () => {
|
|
@@ -779,7 +810,7 @@ class MotionApiService {
|
|
|
779
810
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
780
811
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
781
812
|
});
|
|
782
|
-
throw this.formatApiError(error, 'fetch
|
|
813
|
+
throw this.formatApiError(error, 'fetch', 'user');
|
|
783
814
|
}
|
|
784
815
|
});
|
|
785
816
|
}
|
|
@@ -807,12 +838,14 @@ class MotionApiService {
|
|
|
807
838
|
apiStatus: (0, axios_1.isAxiosError)(error) ? error.response?.status : undefined,
|
|
808
839
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined
|
|
809
840
|
});
|
|
810
|
-
throw this.formatApiError(error, 'fetch
|
|
841
|
+
throw this.formatApiError(error, 'fetch', 'user');
|
|
811
842
|
}
|
|
812
843
|
});
|
|
813
844
|
return cachedUsers[0]; // Return just the user object
|
|
814
845
|
}
|
|
815
|
-
//
|
|
846
|
+
// ========================================
|
|
847
|
+
// SEARCH AND RESOLUTION METHODS
|
|
848
|
+
// ========================================
|
|
816
849
|
/**
|
|
817
850
|
* Resolves a project identifier (either projectId or projectName) to a MotionProject
|
|
818
851
|
* Searches across all workspaces if not found in the specified workspace
|
|
@@ -1058,21 +1091,22 @@ class MotionApiService {
|
|
|
1058
1091
|
// Apply search limit to prevent resource exhaustion
|
|
1059
1092
|
const effectiveLimit = limit || constants_1.LIMITS.MAX_SEARCH_RESULTS;
|
|
1060
1093
|
const lowerQuery = query.toLowerCase();
|
|
1061
|
-
|
|
1094
|
+
const allMatchingTasks = [];
|
|
1062
1095
|
// First, search in the specified workspace
|
|
1063
1096
|
const primaryTasks = await this.getTasks({
|
|
1064
1097
|
workspaceId,
|
|
1065
|
-
limit: effectiveLimit,
|
|
1098
|
+
limit: (0, paginationNew_1.calculateAdaptiveFetchLimit)(allMatchingTasks.length, effectiveLimit),
|
|
1066
1099
|
maxPages: constants_1.LIMITS.MAX_PAGES
|
|
1067
1100
|
});
|
|
1068
1101
|
const primaryMatches = primaryTasks.filter(task => task.name?.toLowerCase().includes(lowerQuery) ||
|
|
1069
1102
|
task.description?.toLowerCase().includes(lowerQuery));
|
|
1070
|
-
allMatchingTasks.push(...primaryMatches);
|
|
1103
|
+
allMatchingTasks.push(...primaryMatches.slice(0, effectiveLimit));
|
|
1071
1104
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Primary workspace search completed', {
|
|
1072
1105
|
method: 'searchTasks',
|
|
1073
1106
|
query,
|
|
1074
1107
|
primaryWorkspaceId: workspaceId,
|
|
1075
|
-
primaryMatches: primaryMatches.length
|
|
1108
|
+
primaryMatches: primaryMatches.length,
|
|
1109
|
+
keptMatches: allMatchingTasks.length
|
|
1076
1110
|
});
|
|
1077
1111
|
// If we haven't reached the limit, search other workspaces
|
|
1078
1112
|
if (allMatchingTasks.length < effectiveLimit) {
|
|
@@ -1083,27 +1117,35 @@ class MotionApiService {
|
|
|
1083
1117
|
if (allMatchingTasks.length >= effectiveLimit)
|
|
1084
1118
|
break;
|
|
1085
1119
|
try {
|
|
1120
|
+
// Calculate fetch limit before API call (defense-in-depth)
|
|
1121
|
+
const fetchLimit = (0, paginationNew_1.calculateAdaptiveFetchLimit)(allMatchingTasks.length, effectiveLimit);
|
|
1122
|
+
if (fetchLimit <= 0)
|
|
1123
|
+
break;
|
|
1086
1124
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Searching additional workspace for tasks', {
|
|
1087
1125
|
method: 'searchTasks',
|
|
1088
1126
|
query,
|
|
1089
1127
|
searchingWorkspaceId: workspace.id,
|
|
1090
|
-
searchingWorkspaceName: workspace.name
|
|
1128
|
+
searchingWorkspaceName: workspace.name,
|
|
1129
|
+
remainingNeeded: effectiveLimit - allMatchingTasks.length
|
|
1091
1130
|
});
|
|
1092
1131
|
const workspaceTasks = await this.getTasks({
|
|
1093
1132
|
workspaceId: workspace.id,
|
|
1094
|
-
limit:
|
|
1133
|
+
limit: fetchLimit,
|
|
1095
1134
|
maxPages: constants_1.LIMITS.MAX_PAGES
|
|
1096
1135
|
});
|
|
1097
1136
|
const workspaceMatches = workspaceTasks.filter(task => task.name?.toLowerCase().includes(lowerQuery) ||
|
|
1098
1137
|
task.description?.toLowerCase().includes(lowerQuery));
|
|
1099
|
-
|
|
1138
|
+
// Only add as many as we still need
|
|
1139
|
+
const remaining = effectiveLimit - allMatchingTasks.length;
|
|
1140
|
+
allMatchingTasks.push(...workspaceMatches.slice(0, remaining));
|
|
1100
1141
|
if (workspaceMatches.length > 0) {
|
|
1101
1142
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Found additional matches in workspace', {
|
|
1102
1143
|
method: 'searchTasks',
|
|
1103
1144
|
query,
|
|
1104
1145
|
workspaceId: workspace.id,
|
|
1105
1146
|
workspaceName: workspace.name,
|
|
1106
|
-
matches: workspaceMatches.length
|
|
1147
|
+
matches: workspaceMatches.length,
|
|
1148
|
+
keptMatches: Math.min(workspaceMatches.length, remaining)
|
|
1107
1149
|
});
|
|
1108
1150
|
}
|
|
1109
1151
|
}
|
|
@@ -1127,17 +1169,14 @@ class MotionApiService {
|
|
|
1127
1169
|
});
|
|
1128
1170
|
}
|
|
1129
1171
|
}
|
|
1130
|
-
//
|
|
1131
|
-
const finalResults = allMatchingTasks.slice(0, effectiveLimit);
|
|
1172
|
+
// Results are already limited during collection, no need to slice again
|
|
1132
1173
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Task search completed across all workspaces', {
|
|
1133
1174
|
method: 'searchTasks',
|
|
1134
1175
|
query,
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
limit: effectiveLimit,
|
|
1138
|
-
crossWorkspaceSearch: allMatchingTasks.length > primaryMatches.length
|
|
1176
|
+
returnedResults: allMatchingTasks.length,
|
|
1177
|
+
limit: effectiveLimit
|
|
1139
1178
|
});
|
|
1140
|
-
return
|
|
1179
|
+
return allMatchingTasks;
|
|
1141
1180
|
}
|
|
1142
1181
|
catch (error) {
|
|
1143
1182
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to search tasks', {
|
|
@@ -1159,17 +1198,21 @@ class MotionApiService {
|
|
|
1159
1198
|
// Apply search limit to prevent resource exhaustion
|
|
1160
1199
|
const effectiveLimit = limit || constants_1.LIMITS.MAX_SEARCH_RESULTS;
|
|
1161
1200
|
const lowerQuery = query.toLowerCase();
|
|
1162
|
-
|
|
1201
|
+
const allMatchingProjects = [];
|
|
1163
1202
|
// First, search in the specified workspace
|
|
1164
|
-
const primaryProjects = await this.getProjects(workspaceId,
|
|
1203
|
+
const primaryProjects = await this.getProjects(workspaceId, {
|
|
1204
|
+
maxPages: constants_1.LIMITS.MAX_PAGES,
|
|
1205
|
+
limit: (0, paginationNew_1.calculateAdaptiveFetchLimit)(allMatchingProjects.length, effectiveLimit)
|
|
1206
|
+
});
|
|
1165
1207
|
const primaryMatches = primaryProjects.filter(project => project.name?.toLowerCase().includes(lowerQuery) ||
|
|
1166
1208
|
project.description?.toLowerCase().includes(lowerQuery));
|
|
1167
|
-
allMatchingProjects.push(...primaryMatches);
|
|
1209
|
+
allMatchingProjects.push(...primaryMatches.slice(0, effectiveLimit));
|
|
1168
1210
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Primary workspace search completed', {
|
|
1169
1211
|
method: 'searchProjects',
|
|
1170
1212
|
query,
|
|
1171
1213
|
primaryWorkspaceId: workspaceId,
|
|
1172
|
-
primaryMatches: primaryMatches.length
|
|
1214
|
+
primaryMatches: primaryMatches.length,
|
|
1215
|
+
keptMatches: allMatchingProjects.length
|
|
1173
1216
|
});
|
|
1174
1217
|
// If we haven't reached the limit, search other workspaces
|
|
1175
1218
|
if (allMatchingProjects.length < effectiveLimit) {
|
|
@@ -1180,23 +1223,34 @@ class MotionApiService {
|
|
|
1180
1223
|
if (allMatchingProjects.length >= effectiveLimit)
|
|
1181
1224
|
break;
|
|
1182
1225
|
try {
|
|
1226
|
+
// Calculate fetch limit before API call (defense-in-depth)
|
|
1227
|
+
const fetchLimit = (0, paginationNew_1.calculateAdaptiveFetchLimit)(allMatchingProjects.length, effectiveLimit);
|
|
1228
|
+
if (fetchLimit <= 0)
|
|
1229
|
+
break;
|
|
1183
1230
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Searching additional workspace for projects', {
|
|
1184
1231
|
method: 'searchProjects',
|
|
1185
1232
|
query,
|
|
1186
1233
|
searchingWorkspaceId: workspace.id,
|
|
1187
|
-
searchingWorkspaceName: workspace.name
|
|
1234
|
+
searchingWorkspaceName: workspace.name,
|
|
1235
|
+
remainingNeeded: effectiveLimit - allMatchingProjects.length
|
|
1236
|
+
});
|
|
1237
|
+
const workspaceProjects = await this.getProjects(workspace.id, {
|
|
1238
|
+
maxPages: constants_1.LIMITS.MAX_PAGES,
|
|
1239
|
+
limit: fetchLimit
|
|
1188
1240
|
});
|
|
1189
|
-
const workspaceProjects = await this.getProjects(workspace.id, constants_1.LIMITS.MAX_PAGES);
|
|
1190
1241
|
const workspaceMatches = workspaceProjects.filter(project => project.name?.toLowerCase().includes(lowerQuery) ||
|
|
1191
1242
|
project.description?.toLowerCase().includes(lowerQuery));
|
|
1192
|
-
|
|
1243
|
+
// Only add as many as we still need
|
|
1244
|
+
const remaining = effectiveLimit - allMatchingProjects.length;
|
|
1245
|
+
allMatchingProjects.push(...workspaceMatches.slice(0, remaining));
|
|
1193
1246
|
if (workspaceMatches.length > 0) {
|
|
1194
1247
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Found additional matches in workspace', {
|
|
1195
1248
|
method: 'searchProjects',
|
|
1196
1249
|
query,
|
|
1197
1250
|
workspaceId: workspace.id,
|
|
1198
1251
|
workspaceName: workspace.name,
|
|
1199
|
-
matches: workspaceMatches.length
|
|
1252
|
+
matches: workspaceMatches.length,
|
|
1253
|
+
keptMatches: Math.min(workspaceMatches.length, remaining)
|
|
1200
1254
|
});
|
|
1201
1255
|
}
|
|
1202
1256
|
}
|
|
@@ -1220,17 +1274,14 @@ class MotionApiService {
|
|
|
1220
1274
|
});
|
|
1221
1275
|
}
|
|
1222
1276
|
}
|
|
1223
|
-
//
|
|
1224
|
-
const finalResults = allMatchingProjects.slice(0, effectiveLimit);
|
|
1277
|
+
// Results are already limited during collection, no need to slice again
|
|
1225
1278
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Project search completed across all workspaces', {
|
|
1226
1279
|
method: 'searchProjects',
|
|
1227
1280
|
query,
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
limit: effectiveLimit,
|
|
1231
|
-
crossWorkspaceSearch: allMatchingProjects.length > primaryMatches.length
|
|
1281
|
+
returnedResults: allMatchingProjects.length,
|
|
1282
|
+
limit: effectiveLimit
|
|
1232
1283
|
});
|
|
1233
|
-
return
|
|
1284
|
+
return allMatchingProjects;
|
|
1234
1285
|
}
|
|
1235
1286
|
catch (error) {
|
|
1236
1287
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to search projects', {
|
|
@@ -1241,6 +1292,9 @@ class MotionApiService {
|
|
|
1241
1292
|
throw error;
|
|
1242
1293
|
}
|
|
1243
1294
|
}
|
|
1295
|
+
// ========================================
|
|
1296
|
+
// COMMENT API METHODS
|
|
1297
|
+
// ========================================
|
|
1244
1298
|
/**
|
|
1245
1299
|
* Get comments for a task with proper pagination support
|
|
1246
1300
|
* @param taskId Task ID to get comments for
|
|
@@ -1287,7 +1341,7 @@ class MotionApiService {
|
|
|
1287
1341
|
taskId,
|
|
1288
1342
|
cursor
|
|
1289
1343
|
});
|
|
1290
|
-
throw this.formatApiError(error, 'fetch
|
|
1344
|
+
throw this.formatApiError(error, 'fetch', 'comment');
|
|
1291
1345
|
}
|
|
1292
1346
|
});
|
|
1293
1347
|
}
|
|
@@ -1319,9 +1373,12 @@ class MotionApiService {
|
|
|
1319
1373
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1320
1374
|
taskId: commentData?.taskId
|
|
1321
1375
|
});
|
|
1322
|
-
throw this.formatApiError(error, 'create comment');
|
|
1376
|
+
throw this.formatApiError(error, 'create', 'comment');
|
|
1323
1377
|
}
|
|
1324
1378
|
}
|
|
1379
|
+
// ========================================
|
|
1380
|
+
// CUSTOM FIELD API METHODS
|
|
1381
|
+
// ========================================
|
|
1325
1382
|
/**
|
|
1326
1383
|
* Fetch custom fields from Motion API
|
|
1327
1384
|
* @param workspaceId - Required workspace ID to get custom fields for
|
|
@@ -1354,7 +1411,7 @@ class MotionApiService {
|
|
|
1354
1411
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1355
1412
|
workspaceId
|
|
1356
1413
|
});
|
|
1357
|
-
throw this.formatApiError(error, 'fetch custom
|
|
1414
|
+
throw this.formatApiError(error, 'fetch', 'custom field');
|
|
1358
1415
|
}
|
|
1359
1416
|
});
|
|
1360
1417
|
}
|
|
@@ -1401,7 +1458,7 @@ class MotionApiService {
|
|
|
1401
1458
|
fieldName: fieldData?.name,
|
|
1402
1459
|
workspaceId
|
|
1403
1460
|
});
|
|
1404
|
-
throw this.formatApiError(error, 'create custom field');
|
|
1461
|
+
throw this.formatApiError(error, 'create', 'custom field', undefined, fieldData.name);
|
|
1405
1462
|
}
|
|
1406
1463
|
}
|
|
1407
1464
|
/**
|
|
@@ -1436,7 +1493,7 @@ class MotionApiService {
|
|
|
1436
1493
|
fieldId,
|
|
1437
1494
|
workspaceId
|
|
1438
1495
|
});
|
|
1439
|
-
throw this.formatApiError(error, 'delete custom field');
|
|
1496
|
+
throw this.formatApiError(error, 'delete', 'custom field', fieldId);
|
|
1440
1497
|
}
|
|
1441
1498
|
}
|
|
1442
1499
|
/**
|
|
@@ -1483,7 +1540,7 @@ class MotionApiService {
|
|
|
1483
1540
|
projectId,
|
|
1484
1541
|
fieldId
|
|
1485
1542
|
});
|
|
1486
|
-
throw this.formatApiError(error, '
|
|
1543
|
+
throw this.formatApiError(error, 'update', 'project', projectId);
|
|
1487
1544
|
}
|
|
1488
1545
|
}
|
|
1489
1546
|
/**
|
|
@@ -1500,8 +1557,7 @@ class MotionApiService {
|
|
|
1500
1557
|
fieldId
|
|
1501
1558
|
});
|
|
1502
1559
|
await this.requestWithRetry(() => this.client.delete(`/projects/${projectId}/custom-fields/${fieldId}`));
|
|
1503
|
-
//
|
|
1504
|
-
// For now, invalidate all project caches
|
|
1560
|
+
// Invalidate all project caches since we don't have workspace context here
|
|
1505
1561
|
this.projectCache.invalidate(`projects:`);
|
|
1506
1562
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Custom field removed from project successfully', {
|
|
1507
1563
|
method: 'removeCustomFieldFromProject',
|
|
@@ -1519,7 +1575,7 @@ class MotionApiService {
|
|
|
1519
1575
|
projectId,
|
|
1520
1576
|
fieldId
|
|
1521
1577
|
});
|
|
1522
|
-
throw this.formatApiError(error, '
|
|
1578
|
+
throw this.formatApiError(error, 'update', 'project', projectId);
|
|
1523
1579
|
}
|
|
1524
1580
|
}
|
|
1525
1581
|
/**
|
|
@@ -1542,10 +1598,7 @@ class MotionApiService {
|
|
|
1542
1598
|
...(value !== undefined && { value })
|
|
1543
1599
|
};
|
|
1544
1600
|
const response = await this.requestWithRetry(() => this.client.post(`/tasks/${taskId}/custom-fields`, requestData));
|
|
1545
|
-
//
|
|
1546
|
-
// if (response.data?.workspaceId) {
|
|
1547
|
-
// this.taskCache.invalidate(`tasks:workspace:${response.data.workspaceId}`);
|
|
1548
|
-
// }
|
|
1601
|
+
// Note: No task cache currently implemented - tasks are not cached due to frequent updates
|
|
1549
1602
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Custom field added to task successfully', {
|
|
1550
1603
|
method: 'addCustomFieldToTask',
|
|
1551
1604
|
taskId,
|
|
@@ -1562,7 +1615,7 @@ class MotionApiService {
|
|
|
1562
1615
|
taskId,
|
|
1563
1616
|
fieldId
|
|
1564
1617
|
});
|
|
1565
|
-
throw this.formatApiError(error, '
|
|
1618
|
+
throw this.formatApiError(error, 'update', 'task', taskId);
|
|
1566
1619
|
}
|
|
1567
1620
|
}
|
|
1568
1621
|
/**
|
|
@@ -1579,8 +1632,7 @@ class MotionApiService {
|
|
|
1579
1632
|
fieldId
|
|
1580
1633
|
});
|
|
1581
1634
|
await this.requestWithRetry(() => this.client.delete(`/tasks/${taskId}/custom-fields/${fieldId}`));
|
|
1582
|
-
//
|
|
1583
|
-
// this.taskCache.invalidate(`tasks:`);
|
|
1635
|
+
// Note: No task cache currently implemented - tasks are not cached due to frequent updates
|
|
1584
1636
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Custom field removed from task successfully', {
|
|
1585
1637
|
method: 'removeCustomFieldFromTask',
|
|
1586
1638
|
taskId,
|
|
@@ -1597,23 +1649,28 @@ class MotionApiService {
|
|
|
1597
1649
|
taskId,
|
|
1598
1650
|
fieldId
|
|
1599
1651
|
});
|
|
1600
|
-
throw this.formatApiError(error, '
|
|
1652
|
+
throw this.formatApiError(error, 'update', 'task', taskId);
|
|
1601
1653
|
}
|
|
1602
1654
|
}
|
|
1655
|
+
// ========================================
|
|
1656
|
+
// RECURRING TASK API METHODS
|
|
1657
|
+
// ========================================
|
|
1603
1658
|
/**
|
|
1604
1659
|
* Fetch recurring tasks from Motion API with automatic pagination
|
|
1605
1660
|
* @param workspaceId - Optional workspace ID to filter recurring tasks
|
|
1606
|
-
* @param
|
|
1661
|
+
* @param options - Optional configuration for maxPages and limit
|
|
1607
1662
|
* @returns Array of recurring tasks from all pages
|
|
1608
1663
|
*/
|
|
1609
|
-
async getRecurringTasks(workspaceId,
|
|
1664
|
+
async getRecurringTasks(workspaceId, options) {
|
|
1665
|
+
const { maxPages = 10, limit } = options || {};
|
|
1610
1666
|
const cacheKey = workspaceId ? `recurring-tasks:workspace:${workspaceId}` : 'recurring-tasks:all';
|
|
1611
1667
|
return this.recurringTaskCache.withCache(cacheKey, async () => {
|
|
1612
1668
|
try {
|
|
1613
1669
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Fetching recurring tasks from Motion API with pagination', {
|
|
1614
1670
|
method: 'getRecurringTasks',
|
|
1615
1671
|
workspaceId,
|
|
1616
|
-
maxPages
|
|
1672
|
+
maxPages,
|
|
1673
|
+
limit
|
|
1617
1674
|
});
|
|
1618
1675
|
// Create a fetch function for pagination utility
|
|
1619
1676
|
const fetchPage = async (cursor) => {
|
|
@@ -1629,7 +1686,8 @@ class MotionApiService {
|
|
|
1629
1686
|
// Use pagination utility to fetch all pages
|
|
1630
1687
|
const paginatedResult = await (0, paginationNew_1.fetchAllPages)(fetchPage, 'recurring-tasks', {
|
|
1631
1688
|
maxPages,
|
|
1632
|
-
logProgress: true
|
|
1689
|
+
logProgress: true,
|
|
1690
|
+
...(limit ? { maxItems: limit } : {})
|
|
1633
1691
|
});
|
|
1634
1692
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'Recurring tasks fetched successfully with pagination', {
|
|
1635
1693
|
method: 'getRecurringTasks',
|
|
@@ -1648,7 +1706,7 @@ class MotionApiService {
|
|
|
1648
1706
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1649
1707
|
workspaceId
|
|
1650
1708
|
});
|
|
1651
|
-
throw this.formatApiError(error, 'fetch recurring
|
|
1709
|
+
throw this.formatApiError(error, 'fetch', 'recurring task');
|
|
1652
1710
|
}
|
|
1653
1711
|
});
|
|
1654
1712
|
}
|
|
@@ -1659,15 +1717,30 @@ class MotionApiService {
|
|
|
1659
1717
|
*/
|
|
1660
1718
|
async createRecurringTask(taskData) {
|
|
1661
1719
|
try {
|
|
1720
|
+
// Validate frequency object before transformation
|
|
1721
|
+
const freqValidation = (0, frequencyTransform_1.validateFrequencyObject)(taskData.frequency);
|
|
1722
|
+
if (!freqValidation.valid) {
|
|
1723
|
+
throw new Error(`Invalid frequency object: ${freqValidation.reason || 'Unknown reason'}`);
|
|
1724
|
+
}
|
|
1725
|
+
// Transform frequency object to API string format
|
|
1726
|
+
const frequencyString = (0, frequencyTransform_1.transformFrequencyToApiString)(taskData.frequency);
|
|
1662
1727
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Creating recurring task in Motion API', {
|
|
1663
1728
|
method: 'createRecurringTask',
|
|
1664
1729
|
name: taskData.name,
|
|
1665
1730
|
assigneeId: taskData.assigneeId,
|
|
1666
|
-
frequency:
|
|
1731
|
+
frequency: frequencyString,
|
|
1732
|
+
originalFrequency: taskData.frequency,
|
|
1667
1733
|
workspaceId: taskData.workspaceId
|
|
1668
1734
|
});
|
|
1735
|
+
// Create payload with transformed frequency while preserving other frequency fields
|
|
1736
|
+
const apiPayload = {
|
|
1737
|
+
...taskData,
|
|
1738
|
+
frequency: frequencyString,
|
|
1739
|
+
// Preserve endDate and other fields that should be sent separately to the API
|
|
1740
|
+
...(taskData.frequency.endDate && { endDate: taskData.frequency.endDate })
|
|
1741
|
+
};
|
|
1669
1742
|
// Create minimal payload by removing empty/null values to avoid validation errors
|
|
1670
|
-
const minimalPayload = (0, constants_1.createMinimalPayload)(
|
|
1743
|
+
const minimalPayload = (0, constants_1.createMinimalPayload)(apiPayload);
|
|
1671
1744
|
const response = await this.requestWithRetry(() => this.client.post('/recurring-tasks', minimalPayload));
|
|
1672
1745
|
// Invalidate cache after successful creation
|
|
1673
1746
|
this.recurringTaskCache.invalidate('recurring-tasks:');
|
|
@@ -1686,7 +1759,7 @@ class MotionApiService {
|
|
|
1686
1759
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1687
1760
|
taskName: taskData?.name
|
|
1688
1761
|
});
|
|
1689
|
-
throw this.formatApiError(error, 'create recurring task');
|
|
1762
|
+
throw this.formatApiError(error, 'create', 'recurring task', undefined, taskData.name);
|
|
1690
1763
|
}
|
|
1691
1764
|
}
|
|
1692
1765
|
/**
|
|
@@ -1717,9 +1790,12 @@ class MotionApiService {
|
|
|
1717
1790
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1718
1791
|
recurringTaskId
|
|
1719
1792
|
});
|
|
1720
|
-
throw this.formatApiError(error, 'delete recurring task');
|
|
1793
|
+
throw this.formatApiError(error, 'delete', 'recurring task', recurringTaskId);
|
|
1721
1794
|
}
|
|
1722
1795
|
}
|
|
1796
|
+
// ========================================
|
|
1797
|
+
// SCHEDULE API METHODS
|
|
1798
|
+
// ========================================
|
|
1723
1799
|
/**
|
|
1724
1800
|
* Get available schedule names for auto-scheduling
|
|
1725
1801
|
* @param workspaceId - Optional workspace ID to filter schedules (currently unused by Motion API)
|
|
@@ -1747,7 +1823,7 @@ class MotionApiService {
|
|
|
1747
1823
|
error: getErrorMessage(error),
|
|
1748
1824
|
workspaceId
|
|
1749
1825
|
});
|
|
1750
|
-
throw this.formatApiError(error, 'fetch
|
|
1826
|
+
throw this.formatApiError(error, 'fetch', 'schedule');
|
|
1751
1827
|
}
|
|
1752
1828
|
}
|
|
1753
1829
|
/**
|
|
@@ -1805,10 +1881,13 @@ class MotionApiService {
|
|
|
1805
1881
|
startDate,
|
|
1806
1882
|
endDate
|
|
1807
1883
|
});
|
|
1808
|
-
throw this.formatApiError(error, 'fetch
|
|
1884
|
+
throw this.formatApiError(error, 'fetch', 'schedule');
|
|
1809
1885
|
}
|
|
1810
1886
|
});
|
|
1811
1887
|
}
|
|
1888
|
+
// ========================================
|
|
1889
|
+
// STATUS API METHODS
|
|
1890
|
+
// ========================================
|
|
1812
1891
|
/**
|
|
1813
1892
|
* Retrieves available workflow statuses from Motion
|
|
1814
1893
|
* @param workspaceId - Optional workspace ID to filter statuses
|
|
@@ -1853,10 +1932,13 @@ class MotionApiService {
|
|
|
1853
1932
|
apiMessage: (0, axios_1.isAxiosError)(error) ? error.response?.data?.message : undefined,
|
|
1854
1933
|
workspaceId
|
|
1855
1934
|
});
|
|
1856
|
-
throw this.formatApiError(error, 'fetch
|
|
1935
|
+
throw this.formatApiError(error, 'fetch', 'status');
|
|
1857
1936
|
}
|
|
1858
1937
|
});
|
|
1859
1938
|
}
|
|
1939
|
+
// ========================================
|
|
1940
|
+
// UTILITY METHODS
|
|
1941
|
+
// ========================================
|
|
1860
1942
|
/**
|
|
1861
1943
|
* Get all uncompleted tasks across all workspaces and projects
|
|
1862
1944
|
* Filters tasks where status.isResolvedStatus is false or undefined
|
|
@@ -1883,10 +1965,14 @@ class MotionApiService {
|
|
|
1883
1965
|
break; // Stop if we've reached the limit
|
|
1884
1966
|
}
|
|
1885
1967
|
try {
|
|
1886
|
-
//
|
|
1968
|
+
// Calculate fetch limit before API call (defense-in-depth)
|
|
1969
|
+
const fetchLimit = (0, paginationNew_1.calculateAdaptiveFetchLimit)(allUncompletedTasks.length, effectiveLimit);
|
|
1970
|
+
if (fetchLimit <= 0)
|
|
1971
|
+
break;
|
|
1972
|
+
// Get tasks from this workspace with adaptive limit
|
|
1887
1973
|
const workspaceTasks = await this.getTasks({
|
|
1888
1974
|
workspaceId: workspace.id,
|
|
1889
|
-
limit:
|
|
1975
|
+
limit: fetchLimit,
|
|
1890
1976
|
maxPages: constants_1.LIMITS.MAX_PAGES
|
|
1891
1977
|
});
|
|
1892
1978
|
// Filter for uncompleted tasks
|
|
@@ -1898,13 +1984,16 @@ class MotionApiService {
|
|
|
1898
1984
|
return true; // Simple string status = assume not resolved
|
|
1899
1985
|
return !task.status.isResolvedStatus; // Object status with isResolvedStatus false
|
|
1900
1986
|
});
|
|
1901
|
-
|
|
1987
|
+
// Only add as many as we still need
|
|
1988
|
+
const remaining = effectiveLimit - allUncompletedTasks.length;
|
|
1989
|
+
allUncompletedTasks.push(...uncompletedTasks.slice(0, remaining));
|
|
1902
1990
|
if (uncompletedTasks.length > 0) {
|
|
1903
1991
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.DEBUG, 'Found uncompleted tasks in workspace', {
|
|
1904
1992
|
method: 'getAllUncompletedTasks',
|
|
1905
1993
|
workspaceId: workspace.id,
|
|
1906
1994
|
workspaceName: workspace.name,
|
|
1907
1995
|
uncompletedTasks: uncompletedTasks.length,
|
|
1996
|
+
keptTasks: Math.min(uncompletedTasks.length, remaining),
|
|
1908
1997
|
totalTasks: workspaceTasks.length
|
|
1909
1998
|
});
|
|
1910
1999
|
}
|
|
@@ -1927,15 +2016,13 @@ class MotionApiService {
|
|
|
1927
2016
|
});
|
|
1928
2017
|
throw workspaceListError;
|
|
1929
2018
|
}
|
|
1930
|
-
//
|
|
1931
|
-
const finalResults = allUncompletedTasks.slice(0, effectiveLimit);
|
|
2019
|
+
// Results are already limited during collection, no need to slice again
|
|
1932
2020
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.INFO, 'All uncompleted tasks fetched successfully', {
|
|
1933
2021
|
method: 'getAllUncompletedTasks',
|
|
1934
|
-
|
|
1935
|
-
returned: finalResults.length,
|
|
2022
|
+
returned: allUncompletedTasks.length,
|
|
1936
2023
|
limit: effectiveLimit
|
|
1937
2024
|
});
|
|
1938
|
-
return
|
|
2025
|
+
return allUncompletedTasks;
|
|
1939
2026
|
}
|
|
1940
2027
|
catch (error) {
|
|
1941
2028
|
(0, logger_1.mcpLog)(constants_1.LOG_LEVELS.ERROR, 'Failed to fetch all uncompleted tasks', {
|