@geanatz/cortex-mcp 5.0.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/dist/errors/errors.d.ts +109 -0
  4. package/dist/errors/errors.js +199 -0
  5. package/dist/errors/index.d.ts +4 -0
  6. package/dist/errors/index.js +4 -0
  7. package/dist/features/task-management/models/artifact.d.ts +169 -0
  8. package/dist/features/task-management/models/artifact.js +155 -0
  9. package/dist/features/task-management/models/config.d.ts +54 -0
  10. package/dist/features/task-management/models/config.js +54 -0
  11. package/dist/features/task-management/models/index.d.ts +6 -0
  12. package/dist/features/task-management/models/index.js +6 -0
  13. package/dist/features/task-management/models/task.d.ts +173 -0
  14. package/dist/features/task-management/models/task.js +84 -0
  15. package/dist/features/task-management/storage/file-storage.d.ts +130 -0
  16. package/dist/features/task-management/storage/file-storage.js +575 -0
  17. package/dist/features/task-management/storage/index.d.ts +5 -0
  18. package/dist/features/task-management/storage/index.js +5 -0
  19. package/dist/features/task-management/storage/storage.d.ts +159 -0
  20. package/dist/features/task-management/storage/storage.js +37 -0
  21. package/dist/features/task-management/tools/artifacts/index.d.ts +6 -0
  22. package/dist/features/task-management/tools/artifacts/index.js +174 -0
  23. package/dist/features/task-management/tools/base/handlers.d.ts +7 -0
  24. package/dist/features/task-management/tools/base/handlers.js +15 -0
  25. package/dist/features/task-management/tools/base/index.d.ts +3 -0
  26. package/dist/features/task-management/tools/base/index.js +3 -0
  27. package/dist/features/task-management/tools/base/schemas.d.ts +3 -0
  28. package/dist/features/task-management/tools/base/schemas.js +6 -0
  29. package/dist/features/task-management/tools/base/types.d.ts +13 -0
  30. package/dist/features/task-management/tools/base/types.js +1 -0
  31. package/dist/features/task-management/tools/tasks/index.d.ts +10 -0
  32. package/dist/features/task-management/tools/tasks/index.js +500 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +57 -0
  35. package/dist/server.d.ts +11 -0
  36. package/dist/server.js +61 -0
  37. package/dist/types/common.d.ts +10 -0
  38. package/dist/types/common.js +1 -0
  39. package/dist/types/index.d.ts +5 -0
  40. package/dist/types/index.js +5 -0
  41. package/dist/utils/cache.d.ts +104 -0
  42. package/dist/utils/cache.js +196 -0
  43. package/dist/utils/file-utils.d.ts +101 -0
  44. package/dist/utils/file-utils.js +270 -0
  45. package/dist/utils/index.d.ts +12 -0
  46. package/dist/utils/index.js +12 -0
  47. package/dist/utils/logger.d.ts +77 -0
  48. package/dist/utils/logger.js +173 -0
  49. package/dist/utils/response-builder.d.ts +4 -0
  50. package/dist/utils/response-builder.js +19 -0
  51. package/dist/utils/storage-config.d.ts +29 -0
  52. package/dist/utils/storage-config.js +51 -0
  53. package/dist/utils/string-utils.d.ts +2 -0
  54. package/dist/utils/string-utils.js +16 -0
  55. package/dist/utils/validation.d.ts +9 -0
  56. package/dist/utils/validation.js +9 -0
  57. package/dist/utils/version.d.ts +9 -0
  58. package/dist/utils/version.js +41 -0
  59. package/package.json +60 -0
@@ -0,0 +1,500 @@
1
+ import { z } from 'zod';
2
+ import { ARTIFACT_PHASES } from '../../models/artifact.js';
3
+ import { withErrorHandling } from '../base/handlers.js';
4
+ import { workingDirectorySchema } from '../base/schemas.js';
5
+ import { getWorkingDirectoryDescription } from '../../../../utils/storage-config.js';
6
+ import { createLogger } from '../../../../utils/logger.js';
7
+ const logger = createLogger('task-tools');
8
+ // ==================== Formatting Helpers ====================
9
+ /**
10
+ * Format a single artifact section for display
11
+ */
12
+ function formatArtifactSection(phase, artifact) {
13
+ const phaseTitle = phase.charAt(0).toUpperCase() + phase.slice(1);
14
+ const header = `## ${phaseTitle} Phase`;
15
+ const metadata = [
16
+ `**Status:** ${artifact.metadata.status}`,
17
+ `**Created:** ${new Date(artifact.metadata.createdAt).toLocaleString()}`,
18
+ `**Updated:** ${new Date(artifact.metadata.updatedAt).toLocaleString()}`
19
+ ];
20
+ if (artifact.metadata.retries !== undefined && artifact.metadata.retries > 0) {
21
+ metadata.push(`**Retries:** ${artifact.metadata.retries}`);
22
+ }
23
+ if (artifact.metadata.error) {
24
+ metadata.push(`**Error:** ${artifact.metadata.error}`);
25
+ }
26
+ return `${header}\n${metadata.join(' | ')}\n\n${artifact.content}`;
27
+ }
28
+ /**
29
+ * Count total subtasks in a task
30
+ */
31
+ function countSubtasks(task) {
32
+ return task.subtasks?.length || 0;
33
+ }
34
+ /**
35
+ * Count done subtasks in a task
36
+ */
37
+ function countDoneSubtasks(task) {
38
+ return task.subtasks?.filter(s => s.status === 'done').length || 0;
39
+ }
40
+ // ==================== Tool Factories ====================
41
+ /**
42
+ * Create all task management tools using the same factory pattern as artifact tools.
43
+ * Each tool creates its own storage instance per-call using the provided factory.
44
+ */
45
+ export function createTaskTools(config, createStorage) {
46
+ const wdSchema = workingDirectorySchema.describe(getWorkingDirectoryDescription(config));
47
+ return [
48
+ createListTasksTool(wdSchema, config, createStorage),
49
+ createCreateTaskTool(wdSchema, config, createStorage),
50
+ createGetTaskTool(wdSchema, config, createStorage),
51
+ createUpdateTaskTool(wdSchema, config, createStorage),
52
+ createDeleteTaskTool(wdSchema, config, createStorage),
53
+ // REMOVED: createMoveTaskTool - no longer needed in simplified model
54
+ ];
55
+ }
56
+ // ==================== List Tasks ====================
57
+ function createListTasksTool(wdSchema, config, createStorage) {
58
+ const handler = async ({ workingDirectory, showHierarchy = true, includeDone = true }) => {
59
+ try {
60
+ const storage = await createStorage(workingDirectory, config);
61
+ if (showHierarchy) {
62
+ const hierarchy = await storage.getTaskHierarchy();
63
+ if (hierarchy.length === 0) {
64
+ return {
65
+ content: [{ type: 'text', text: 'No tasks found. Create your first task using create_task.' }]
66
+ };
67
+ }
68
+ const formatTaskHierarchy = (hierarchyList) => {
69
+ return hierarchyList.map(item => {
70
+ const task = item.task;
71
+ if (!includeDone && task.status === 'done')
72
+ return '';
73
+ const icon = task.status === 'done' ? '✅' : '⏳';
74
+ const statusText = ` [${task.status.toUpperCase()}]`;
75
+ const subtaskSummary = task.subtasks?.length
76
+ ? ` (${countDoneSubtasks(task)}/${countSubtasks(task)} subtasks done)`
77
+ : '';
78
+ let taskLine = `${icon} **${task.id}**${statusText}${subtaskSummary}\n`;
79
+ taskLine += ` ${task.details}\n`;
80
+ if (task.tags && task.tags.length > 0) {
81
+ taskLine += ` Tags: ${task.tags.join(', ')}\n`;
82
+ }
83
+ // Show subtasks indented
84
+ if (task.subtasks && task.subtasks.length > 0) {
85
+ const visibleSubtasks = includeDone
86
+ ? task.subtasks
87
+ : task.subtasks.filter(s => s.status !== 'done');
88
+ if (visibleSubtasks.length > 0) {
89
+ taskLine += '\n 📋 Subtasks:\n';
90
+ for (const subtask of visibleSubtasks) {
91
+ const subIcon = subtask.status === 'done' ? '✅' : '⏳';
92
+ taskLine += ` ${subIcon} [${subtask.id}] ${subtask.details}\n`;
93
+ }
94
+ }
95
+ }
96
+ return taskLine;
97
+ }).filter(line => line.trim()).join('\n');
98
+ };
99
+ const hierarchyText = formatTaskHierarchy(hierarchy);
100
+ const totalTasks = hierarchy.length;
101
+ const totalSubtasks = hierarchy.reduce((sum, h) => sum + countSubtasks(h.task), 0);
102
+ const doneTasks = hierarchy.filter(h => h.task.status === 'done').length;
103
+ const doneSubtasks = hierarchy.reduce((sum, h) => sum + countDoneSubtasks(h.task), 0);
104
+ return {
105
+ content: [{
106
+ type: 'text',
107
+ text: `🌲 **Task Hierarchy**
108
+
109
+ Total: ${totalTasks} tasks with ${totalSubtasks} subtasks (${doneTasks} tasks, ${doneSubtasks} subtasks done)
110
+
111
+ ${hierarchyText}
112
+
113
+ 💡 **Tips:**
114
+ • Use \`create_task\` to add new tasks
115
+ • Use \`update_task\` with addSubtask to break down tasks
116
+ • Use \`list_tasks\` with includeDone: false to hide completed work`
117
+ }]
118
+ };
119
+ }
120
+ else {
121
+ const tasks = await storage.getTasks();
122
+ if (tasks.length === 0) {
123
+ return {
124
+ content: [{ type: 'text', text: 'No tasks found. Create your first task using create_task.' }]
125
+ };
126
+ }
127
+ const filteredTasks = includeDone ? [...tasks] : tasks.filter(t => t.status !== 'done');
128
+ const taskList = filteredTasks.map(task => {
129
+ const icon = task.status === 'done' ? '✅' : '⏳';
130
+ const statusText = ` [${task.status.toUpperCase()}]`;
131
+ const subtaskInfo = task.subtasks?.length
132
+ ? ` (${countDoneSubtasks(task)}/${countSubtasks(task)} subtasks)`
133
+ : '';
134
+ return `${icon} **${task.id}**${statusText}${subtaskInfo}
135
+ ${task.details}
136
+ ${task.tags && task.tags.length > 0 ? `Tags: ${task.tags.join(', ')}` : ''}
137
+ Created: ${new Date(task.createdAt).toLocaleString()}`;
138
+ }).join('\n\n');
139
+ const doneCount = filteredTasks.filter(t => t.status === 'done').length;
140
+ return {
141
+ content: [{
142
+ type: 'text',
143
+ text: `📋 **Tasks**
144
+
145
+ Found ${filteredTasks.length} task(s) (${doneCount} done):
146
+
147
+ ${taskList}
148
+
149
+ 💡 Use \`list_tasks\` with \`showHierarchy: true\` to see subtasks.`
150
+ }]
151
+ };
152
+ }
153
+ }
154
+ catch (error) {
155
+ logger.error('Error in list_tasks', error);
156
+ return {
157
+ content: [{ type: 'text', text: `Error listing tasks: ${error instanceof Error ? error.message : 'Unknown error'}` }],
158
+ isError: true
159
+ };
160
+ }
161
+ };
162
+ return {
163
+ name: 'list_tasks',
164
+ description: 'List all tasks with hierarchical display including subtasks. Perfect for understanding current workflow state and task organization.',
165
+ parameters: {
166
+ workingDirectory: wdSchema,
167
+ showHierarchy: z.boolean().optional().describe('Show tasks in hierarchical tree format with subtasks (default: true)'),
168
+ includeDone: z.boolean().optional().describe('Include done tasks in results (default: true)')
169
+ },
170
+ handler: withErrorHandling(handler)
171
+ };
172
+ }
173
+ // ==================== Create Task ====================
174
+ function createCreateTaskTool(wdSchema, config, createStorage) {
175
+ const handler = async ({ workingDirectory, details, status, tags }) => {
176
+ try {
177
+ const storage = await createStorage(workingDirectory, config);
178
+ const createdTask = await storage.createTask({
179
+ details: details.trim(),
180
+ status,
181
+ tags,
182
+ });
183
+ return {
184
+ content: [{
185
+ type: 'text',
186
+ text: `✅ Task created successfully!
187
+
188
+ **${createdTask.id}**
189
+
190
+ 📋 **Task Details:**
191
+ • Details: ${createdTask.details}
192
+ • Status: ${createdTask.status}
193
+ • Tags: ${createdTask.tags?.join(', ') || 'None'}
194
+ • Subtasks: None (add with update_task)
195
+ • Created: ${new Date(createdTask.createdAt).toLocaleString()}
196
+
197
+ 🎯 **Next Steps:**
198
+ • Break down into subtasks using update_task with addSubtask
199
+ • Update progress using \`update_task\` as you work
200
+ • Add phase artifacts (explore, plan, build, test)`
201
+ }]
202
+ };
203
+ }
204
+ catch (error) {
205
+ logger.error('Error in create_task', error);
206
+ return {
207
+ content: [{ type: 'text', text: `Error creating task: ${error instanceof Error ? error.message : 'Unknown error'}` }],
208
+ isError: true
209
+ };
210
+ }
211
+ };
212
+ return {
213
+ name: 'create_task',
214
+ description: 'Create a new task for the orchestration workflow. Task ID is auto-generated from details. Use update_task with addSubtask to create subtasks.',
215
+ parameters: {
216
+ workingDirectory: wdSchema,
217
+ details: z.string().describe('Task description - used to generate the task ID (e.g., "Implement authentication" becomes "001-implement-authentication")'),
218
+ status: z.enum(['pending', 'in_progress', 'done']).optional().describe('Initial task status (defaults to pending)'),
219
+ tags: z.array(z.string()).optional().describe('Tags for categorization and filtering')
220
+ },
221
+ handler: withErrorHandling(handler)
222
+ };
223
+ }
224
+ // ==================== Get Task ====================
225
+ function createGetTaskTool(wdSchema, config, createStorage) {
226
+ const handler = async ({ workingDirectory, id }) => {
227
+ try {
228
+ const storage = await createStorage(workingDirectory, config);
229
+ const task = await storage.getTask(id.trim());
230
+ if (!task) {
231
+ return {
232
+ content: [{ type: 'text', text: `Error: Task with ID "${id}" not found. Use list_tasks to see all available tasks.` }],
233
+ isError: true
234
+ };
235
+ }
236
+ const artifacts = await storage.getAllArtifacts(task.id);
237
+ // Build subtasks section
238
+ let subtasksSection = '';
239
+ if (task.subtasks && task.subtasks.length > 0) {
240
+ const doneCount = countDoneSubtasks(task);
241
+ subtasksSection = `\n## Subtasks (${doneCount}/${task.subtasks.length} done)\n\n`;
242
+ for (const subtask of task.subtasks) {
243
+ const icon = subtask.status === 'done' ? '✅' : '⏳';
244
+ subtasksSection += `- ${icon} **[${subtask.id}]** ${subtask.details} (${subtask.status})\n`;
245
+ }
246
+ }
247
+ else {
248
+ subtasksSection = '\n## Subtasks\n\nNo subtasks yet. Use update_task with addSubtask to create them.\n';
249
+ }
250
+ const artifactSummary = ARTIFACT_PHASES.map(phase => {
251
+ const artifact = artifacts[phase];
252
+ if (artifact) {
253
+ return ` - **${phase}:** ${artifact.metadata.status}${artifact.metadata.retries ? ` (retries: ${artifact.metadata.retries})` : ''}`;
254
+ }
255
+ return ` - **${phase}:** Not started`;
256
+ }).join('\n');
257
+ const artifactSections = ARTIFACT_PHASES.map(phase => {
258
+ const artifact = artifacts[phase];
259
+ if (artifact)
260
+ return formatArtifactSection(phase, artifact);
261
+ return null;
262
+ }).filter(Boolean).join('\n\n');
263
+ const taskInfo = `# Task: ${task.id}
264
+
265
+ ## Metadata
266
+ - **Status:** ${task.status}
267
+ - **Details:** ${task.details}
268
+ - **Tags:** ${task.tags?.join(', ') || 'None'}
269
+ - **Actual Hours:** ${task.actualHours || 'Not set'}
270
+ - **Created:** ${new Date(task.createdAt).toLocaleString()}
271
+ - **Updated:** ${new Date(task.updatedAt).toLocaleString()}
272
+ ${subtasksSection}
273
+
274
+ ## Phase Artifacts
275
+ ${artifactSummary}`;
276
+ const fullOutput = artifactSections
277
+ ? `${taskInfo}\n\n---\n\n${artifactSections}`
278
+ : `${taskInfo}\n\n---\n\n*No artifacts created yet. Run explore phase to start.*`;
279
+ return {
280
+ content: [{ type: 'text', text: fullOutput }]
281
+ };
282
+ }
283
+ catch (error) {
284
+ logger.error('Error in get_task', error);
285
+ return {
286
+ content: [{ type: 'text', text: `Error retrieving task: ${error instanceof Error ? error.message : 'Unknown error'}` }],
287
+ isError: true
288
+ };
289
+ }
290
+ };
291
+ return {
292
+ name: 'get_task',
293
+ description: 'Retrieve complete task details including all subtasks and phase artifacts (explore, search, plan, build, test). Essential for understanding current task state and accumulated knowledge.',
294
+ parameters: {
295
+ workingDirectory: wdSchema,
296
+ id: z.string().describe('The unique identifier of the task to retrieve')
297
+ },
298
+ handler: withErrorHandling(handler)
299
+ };
300
+ }
301
+ // ==================== Update Task ====================
302
+ function createUpdateTaskTool(wdSchema, config, createStorage) {
303
+ const handler = async ({ workingDirectory, id, details, status, tags, actualHours, addSubtask, updateSubtask, removeSubtaskId }) => {
304
+ try {
305
+ const storage = await createStorage(workingDirectory, config);
306
+ if (details !== undefined && details.trim().length === 0) {
307
+ return {
308
+ content: [{ type: 'text', text: 'Error: Task details must not be empty.' }],
309
+ isError: true
310
+ };
311
+ }
312
+ if (details === undefined && status === undefined && tags === undefined &&
313
+ actualHours === undefined && addSubtask === undefined &&
314
+ updateSubtask === undefined && removeSubtaskId === undefined) {
315
+ return {
316
+ content: [{ type: 'text', text: 'Error: At least one field must be provided for update.' }],
317
+ isError: true
318
+ };
319
+ }
320
+ const existingTask = await storage.getTask(id.trim());
321
+ if (!existingTask) {
322
+ return {
323
+ content: [{ type: 'text', text: `Error: Task with ID "${id}" not found. Use list_tasks to see all available tasks.` }],
324
+ isError: true
325
+ };
326
+ }
327
+ // Validate subtask operations
328
+ if (addSubtask && addSubtask.details.trim().length === 0) {
329
+ return {
330
+ content: [{ type: 'text', text: 'Error: Subtask details must not be empty.' }],
331
+ isError: true
332
+ };
333
+ }
334
+ if (updateSubtask) {
335
+ if (updateSubtask.id.trim().length === 0) {
336
+ return {
337
+ content: [{ type: 'text', text: 'Error: Subtask ID is required for update.' }],
338
+ isError: true
339
+ };
340
+ }
341
+ const existingSubtask = existingTask.subtasks?.find(s => s.id === updateSubtask.id);
342
+ if (!existingSubtask) {
343
+ return {
344
+ content: [{ type: 'text', text: `Error: Subtask with ID "${updateSubtask.id}" not found in task "${id}".` }],
345
+ isError: true
346
+ };
347
+ }
348
+ }
349
+ const updatedTask = await storage.updateTask(id, {
350
+ details: details?.trim(),
351
+ status,
352
+ tags,
353
+ actualHours,
354
+ addSubtask: addSubtask ? {
355
+ details: addSubtask.details.trim(),
356
+ status: addSubtask.status,
357
+ } : undefined,
358
+ updateSubtask: updateSubtask ? {
359
+ id: updateSubtask.id.trim(),
360
+ details: updateSubtask.details?.trim(),
361
+ status: updateSubtask.status,
362
+ } : undefined,
363
+ removeSubtaskId: removeSubtaskId?.trim(),
364
+ });
365
+ if (!updatedTask) {
366
+ return {
367
+ content: [{ type: 'text', text: `Error: Failed to update task with ID "${id}".` }],
368
+ isError: true
369
+ };
370
+ }
371
+ const changedFields = [];
372
+ if (details !== undefined)
373
+ changedFields.push('details');
374
+ if (status !== undefined)
375
+ changedFields.push('status');
376
+ if (tags !== undefined)
377
+ changedFields.push('tags');
378
+ if (actualHours !== undefined)
379
+ changedFields.push('actual hours');
380
+ if (addSubtask !== undefined)
381
+ changedFields.push('added subtask');
382
+ if (updateSubtask !== undefined)
383
+ changedFields.push('updated subtask');
384
+ if (removeSubtaskId !== undefined)
385
+ changedFields.push('removed subtask');
386
+ const subtaskSummary = updatedTask.subtasks?.length
387
+ ? `${countDoneSubtasks(updatedTask)}/${countSubtasks(updatedTask)} done`
388
+ : 'None';
389
+ return {
390
+ content: [{
391
+ type: 'text',
392
+ text: `✅ Task updated successfully!
393
+
394
+ **${updatedTask.id}**
395
+
396
+ 📋 **Task Properties:**
397
+ • Status: ${updatedTask.status}
398
+ • Tags: ${updatedTask.tags?.join(', ') || 'None'}
399
+ • Actual Hours: ${updatedTask.actualHours || 'Not set'}
400
+ • Subtasks: ${subtaskSummary}
401
+ • Details: ${updatedTask.details}
402
+ • Last Updated: ${new Date(updatedTask.updatedAt).toLocaleString()}
403
+
404
+ ✏️ **Updated:** ${changedFields.join(', ')}
405
+
406
+ 🎯 **Next Steps:**
407
+ • Continue adding subtasks with update_task
408
+ • Mark subtasks as done to track progress
409
+ • Update progress using \`update_task\``
410
+ }]
411
+ };
412
+ }
413
+ catch (error) {
414
+ logger.error('Error in update_task', error);
415
+ return {
416
+ content: [{ type: 'text', text: `Error updating task: ${error instanceof Error ? error.message : 'Unknown error'}` }],
417
+ isError: true
418
+ };
419
+ }
420
+ };
421
+ return {
422
+ name: 'update_task',
423
+ description: 'Update task properties including status, details, tags, and manage subtasks. Use addSubtask to break down work, updateSubtask to update subtask status, and removeSubtaskId to remove subtasks.',
424
+ parameters: {
425
+ workingDirectory: wdSchema,
426
+ id: z.string().describe('The unique identifier of the task to update'),
427
+ details: z.string().optional().describe('Updated task description (optional)'),
428
+ status: z.enum(['pending', 'in_progress', 'done']).optional().describe('Updated task status'),
429
+ tags: z.array(z.string()).optional().describe('Updated tags for categorization and filtering'),
430
+ actualHours: z.number().min(0).optional().describe('Actual time spent on the task in hours'),
431
+ addSubtask: z.object({
432
+ details: z.string().describe('Subtask description'),
433
+ status: z.enum(['pending', 'in_progress', 'done']).optional().describe('Subtask status (defaults to pending)')
434
+ }).optional().describe('Add a new subtask to this task'),
435
+ updateSubtask: z.object({
436
+ id: z.string().describe('ID of the subtask to update'),
437
+ details: z.string().optional().describe('Updated subtask description'),
438
+ status: z.enum(['pending', 'in_progress', 'done']).optional().describe('Updated subtask status')
439
+ }).optional().describe('Update an existing subtask'),
440
+ removeSubtaskId: z.string().optional().describe('ID of the subtask to remove')
441
+ },
442
+ handler: withErrorHandling(handler)
443
+ };
444
+ }
445
+ // ==================== Delete Task ====================
446
+ function createDeleteTaskTool(wdSchema, config, createStorage) {
447
+ const handler = async ({ workingDirectory, id, confirm }) => {
448
+ try {
449
+ const storage = await createStorage(workingDirectory, config);
450
+ if (confirm !== true) {
451
+ return {
452
+ content: [{ type: 'text', text: 'Error: You must set confirm to true to delete a task.' }],
453
+ isError: true
454
+ };
455
+ }
456
+ const task = await storage.getTask(id.trim());
457
+ if (!task) {
458
+ return {
459
+ content: [{ type: 'text', text: `Error: Task with ID "${id}" not found. Use list_tasks to see all available tasks.` }],
460
+ isError: true
461
+ };
462
+ }
463
+ const deleted = await storage.deleteTask(id);
464
+ if (!deleted) {
465
+ return {
466
+ content: [{ type: 'text', text: `Error: Failed to delete task with ID "${id}".` }],
467
+ isError: true
468
+ };
469
+ }
470
+ return {
471
+ content: [{
472
+ type: 'text',
473
+ text: `✅ Task deleted successfully!
474
+
475
+ **Deleted:** "${task.id}"
476
+ **Subtasks deleted:** ${countSubtasks(task)}
477
+
478
+ This action cannot be undone. All data associated with this task and its subtasks has been permanently removed.`
479
+ }]
480
+ };
481
+ }
482
+ catch (error) {
483
+ logger.error('Error in delete_task', error);
484
+ return {
485
+ content: [{ type: 'text', text: `Error deleting task: ${error instanceof Error ? error.message : 'Unknown error'}` }],
486
+ isError: true
487
+ };
488
+ }
489
+ };
490
+ return {
491
+ name: 'delete_task',
492
+ description: 'Delete a task and all its subtasks. Requires confirmation to prevent accidental deletion.',
493
+ parameters: {
494
+ workingDirectory: wdSchema,
495
+ id: z.string().describe('The unique identifier of the task to delete'),
496
+ confirm: z.boolean().describe('Must be set to true to confirm deletion (safety measure)')
497
+ },
498
+ handler: withErrorHandling(handler)
499
+ };
500
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // CRITICAL: Set up error handlers FIRST, before any imports that could fail
3
+ // This prevents the process from crashing on uncaught errors
4
+ process.on('uncaughtException', (error) => {
5
+ console.error('[FATAL] Uncaught exception:', error);
6
+ // Don't exit immediately - give stderr time to flush
7
+ setTimeout(() => process.exit(1), 100);
8
+ });
9
+ process.on('unhandledRejection', (reason, promise) => {
10
+ console.error('[FATAL] Unhandled rejection at:', promise, 'reason:', reason);
11
+ // Don't exit immediately - give stderr time to flush
12
+ setTimeout(() => process.exit(1), 100);
13
+ });
14
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
15
+ import { createServer } from './server.js';
16
+ import { getVersionString } from './utils/version.js';
17
+ import { parseCommandLineArgs } from './utils/storage-config.js';
18
+ import { logger, LogLevel } from './utils/logger.js';
19
+ async function main() {
20
+ try {
21
+ const storageConfig = parseCommandLineArgs();
22
+ if (process.env.DEBUG === 'true') {
23
+ logger.setLevel(LogLevel.DEBUG);
24
+ }
25
+ const server = await createServer(storageConfig);
26
+ const transport = new StdioServerTransport();
27
+ // Connect to transport with error handling
28
+ await server.connect(transport);
29
+ logger.info(`Cortex MCP Server ${getVersionString()} started successfully`);
30
+ if (storageConfig.useGlobalDirectory) {
31
+ logger.info('Global directory mode: Using ~/.cortex/ for all data storage');
32
+ }
33
+ else {
34
+ logger.info('Project-specific mode: Using .cortex/ within each working directory');
35
+ }
36
+ logger.info('Task Management: list, create, get, update, delete');
37
+ logger.info('Artifact Support: explore, search, plan, build, test phases');
38
+ logger.info('Use list_tasks to get started!');
39
+ }
40
+ catch (error) {
41
+ logger.error('Failed to start MCP server', error);
42
+ // Give logger time to write to stderr before exiting
43
+ setTimeout(() => process.exit(1), 100);
44
+ }
45
+ }
46
+ process.on('SIGINT', () => {
47
+ logger.info('Shutting down MCP server...');
48
+ setTimeout(() => process.exit(0), 50);
49
+ });
50
+ process.on('SIGTERM', () => {
51
+ logger.info('Shutting down MCP server...');
52
+ setTimeout(() => process.exit(0), 50);
53
+ });
54
+ main().catch((error) => {
55
+ logger.error('Unhandled error in main', error);
56
+ setTimeout(() => process.exit(1), 100);
57
+ });
@@ -0,0 +1,11 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { StorageConfig } from './utils/storage-config.js';
3
+ /**
4
+ * Create and configure the MCP server for task management with artifact support.
5
+ *
6
+ * All tools follow a uniform registration pattern:
7
+ * 1. Factory functions return ToolDefinition[] with name, description, parameters, handler
8
+ * 2. Each tool creates its own storage instance per-call via the shared StorageFactory
9
+ * 3. Tools are registered in a single loop — no inline tool declarations
10
+ */
11
+ export declare function createServer(config?: StorageConfig): Promise<McpServer>;
package/dist/server.js ADDED
@@ -0,0 +1,61 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { FileStorage } from './features/task-management/storage/file-storage.js';
3
+ import { getVersion } from './utils/version.js';
4
+ import { resolveWorkingDirectory } from './utils/storage-config.js';
5
+ import { createLogger } from './utils/logger.js';
6
+ // Tool factories
7
+ import { createTaskTools } from './features/task-management/tools/tasks/index.js';
8
+ import { createArtifactTools } from './features/task-management/tools/artifacts/index.js';
9
+ const logger = createLogger('server');
10
+ /**
11
+ * Create and initialize a FileStorage instance for a specific working directory.
12
+ */
13
+ async function createStorage(workingDirectory, config) {
14
+ const resolvedDirectory = resolveWorkingDirectory(workingDirectory, config);
15
+ const storage = new FileStorage(resolvedDirectory);
16
+ await storage.initialize();
17
+ return storage;
18
+ }
19
+ /**
20
+ * Create and configure the MCP server for task management with artifact support.
21
+ *
22
+ * All tools follow a uniform registration pattern:
23
+ * 1. Factory functions return ToolDefinition[] with name, description, parameters, handler
24
+ * 2. Each tool creates its own storage instance per-call via the shared StorageFactory
25
+ * 3. Tools are registered in a single loop — no inline tool declarations
26
+ */
27
+ export async function createServer(config = { useGlobalDirectory: false }) {
28
+ try {
29
+ logger.info('Creating MCP server', { config });
30
+ const version = getVersion();
31
+ logger.debug('Loaded version', { version });
32
+ const server = new McpServer({
33
+ name: '@geanatz/cortex-mcp',
34
+ version
35
+ });
36
+ // Build all tool definitions from factories
37
+ const taskTools = createTaskTools(config, createStorage);
38
+ const artifactTools = createArtifactTools(config, createStorage);
39
+ const allTools = [...taskTools, ...artifactTools];
40
+ logger.debug('Registering tools', { count: allTools.length });
41
+ // Register every tool uniformly
42
+ for (const tool of allTools) {
43
+ try {
44
+ server.tool(tool.name, tool.description, tool.parameters, tool.handler);
45
+ }
46
+ catch (error) {
47
+ logger.error(`Failed to register tool: ${tool.name}`, error);
48
+ throw error;
49
+ }
50
+ }
51
+ logger.info('MCP server created successfully', {
52
+ taskTools: taskTools.length,
53
+ artifactTools: artifactTools.length
54
+ });
55
+ return server;
56
+ }
57
+ catch (error) {
58
+ logger.error('Failed to create MCP server', error);
59
+ throw error;
60
+ }
61
+ }
@@ -0,0 +1,10 @@
1
+ export interface McpTextContent {
2
+ type: 'text';
3
+ text: string;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface McpToolResponse {
7
+ content: McpTextContent[];
8
+ isError?: boolean;
9
+ [key: string]: unknown;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Central type exports
3
+ * All shared types should be imported from this module
4
+ */
5
+ export * from './common.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Central type exports
3
+ * All shared types should be imported from this module
4
+ */
5
+ export * from './common.js';