@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.
- package/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/errors/errors.d.ts +109 -0
- package/dist/errors/errors.js +199 -0
- package/dist/errors/index.d.ts +4 -0
- package/dist/errors/index.js +4 -0
- package/dist/features/task-management/models/artifact.d.ts +169 -0
- package/dist/features/task-management/models/artifact.js +155 -0
- package/dist/features/task-management/models/config.d.ts +54 -0
- package/dist/features/task-management/models/config.js +54 -0
- package/dist/features/task-management/models/index.d.ts +6 -0
- package/dist/features/task-management/models/index.js +6 -0
- package/dist/features/task-management/models/task.d.ts +173 -0
- package/dist/features/task-management/models/task.js +84 -0
- package/dist/features/task-management/storage/file-storage.d.ts +130 -0
- package/dist/features/task-management/storage/file-storage.js +575 -0
- package/dist/features/task-management/storage/index.d.ts +5 -0
- package/dist/features/task-management/storage/index.js +5 -0
- package/dist/features/task-management/storage/storage.d.ts +159 -0
- package/dist/features/task-management/storage/storage.js +37 -0
- package/dist/features/task-management/tools/artifacts/index.d.ts +6 -0
- package/dist/features/task-management/tools/artifacts/index.js +174 -0
- package/dist/features/task-management/tools/base/handlers.d.ts +7 -0
- package/dist/features/task-management/tools/base/handlers.js +15 -0
- package/dist/features/task-management/tools/base/index.d.ts +3 -0
- package/dist/features/task-management/tools/base/index.js +3 -0
- package/dist/features/task-management/tools/base/schemas.d.ts +3 -0
- package/dist/features/task-management/tools/base/schemas.js +6 -0
- package/dist/features/task-management/tools/base/types.d.ts +13 -0
- package/dist/features/task-management/tools/base/types.js +1 -0
- package/dist/features/task-management/tools/tasks/index.d.ts +10 -0
- package/dist/features/task-management/tools/tasks/index.js +500 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +57 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +61 -0
- package/dist/types/common.d.ts +10 -0
- package/dist/types/common.js +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.js +5 -0
- package/dist/utils/cache.d.ts +104 -0
- package/dist/utils/cache.js +196 -0
- package/dist/utils/file-utils.d.ts +101 -0
- package/dist/utils/file-utils.js +270 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.js +12 -0
- package/dist/utils/logger.d.ts +77 -0
- package/dist/utils/logger.js +173 -0
- package/dist/utils/response-builder.d.ts +4 -0
- package/dist/utils/response-builder.js +19 -0
- package/dist/utils/storage-config.d.ts +29 -0
- package/dist/utils/storage-config.js +51 -0
- package/dist/utils/string-utils.d.ts +2 -0
- package/dist/utils/string-utils.js +16 -0
- package/dist/utils/validation.d.ts +9 -0
- package/dist/utils/validation.js +9 -0
- package/dist/utils/version.d.ts +9 -0
- package/dist/utils/version.js +41 -0
- 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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
});
|
package/dist/server.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
export {};
|