taskman-mcp 0.1.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/app.js +487 -0
- package/package.json +16 -0
package/app.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
|
|
9
|
+
const DATA_PATH = path.join(os.homedir(), '.taskman', 'data.json');
|
|
10
|
+
|
|
11
|
+
function loadStore() {
|
|
12
|
+
try {
|
|
13
|
+
if (fs.existsSync(DATA_PATH)) {
|
|
14
|
+
const store = JSON.parse(fs.readFileSync(DATA_PATH, 'utf8'));
|
|
15
|
+
migrateStore(store);
|
|
16
|
+
return store;
|
|
17
|
+
}
|
|
18
|
+
} catch (e) {}
|
|
19
|
+
return { tasks: [], ideas: [], completed_tasks: [], completed_ideas: [] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function migrateStore(store) {
|
|
23
|
+
let changed = false;
|
|
24
|
+
for (const task of (store.tasks || [])) {
|
|
25
|
+
if (task.notes && !task.content) {
|
|
26
|
+
task.content = task.notes;
|
|
27
|
+
delete task.notes;
|
|
28
|
+
changed = true;
|
|
29
|
+
}
|
|
30
|
+
if (!task.status) {
|
|
31
|
+
task.status = task.completed ? 'completed' : 'pending';
|
|
32
|
+
changed = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const idea of (store.ideas || [])) {
|
|
36
|
+
if (idea.notes && !idea.content) {
|
|
37
|
+
idea.content = idea.notes;
|
|
38
|
+
delete idea.notes;
|
|
39
|
+
changed = true;
|
|
40
|
+
}
|
|
41
|
+
if (!idea.status) {
|
|
42
|
+
idea.status = 'pending';
|
|
43
|
+
changed = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (changed) saveStore(store);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function saveStore(store) {
|
|
50
|
+
const dir = path.dirname(DATA_PATH);
|
|
51
|
+
if (!fs.existsSync(dir)) {
|
|
52
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
fs.writeFileSync(DATA_PATH, JSON.stringify(store, null, 2));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function generateId() {
|
|
58
|
+
const now = new Date();
|
|
59
|
+
return now.toISOString().replace(/[-:T]/g, '').replace(/\..+/, '') + '.' + String(now.getMilliseconds()).padStart(6, '0');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getStatusIcon(status) {
|
|
63
|
+
switch (status) {
|
|
64
|
+
case 'in_progress': return '◐';
|
|
65
|
+
case 'completed': return '◉';
|
|
66
|
+
case 'archived': return '◉';
|
|
67
|
+
default: return '○';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function getProgress(task) {
|
|
72
|
+
if (!task.subtasks || task.subtasks.length === 0) return '';
|
|
73
|
+
const completed = task.subtasks.filter(s => s.status === 'completed').length;
|
|
74
|
+
return ` [${completed}/${task.subtasks.length}]`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const server = new Server(
|
|
78
|
+
{ name: 'taskman-mcp', version: '0.2.0' },
|
|
79
|
+
{ capabilities: { tools: {} } }
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
83
|
+
tools: [
|
|
84
|
+
{
|
|
85
|
+
name: 'mcp__taskman_task_list',
|
|
86
|
+
description: 'List all tasks. Filter by all, pending, or completed.',
|
|
87
|
+
inputSchema: {
|
|
88
|
+
type: 'object',
|
|
89
|
+
properties: {
|
|
90
|
+
filter: { type: 'string', description: 'Filter: all, pending, or completed', enum: ['all', 'pending', 'completed'] }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'mcp__taskman_task_get',
|
|
96
|
+
description: 'Get details of a specific task by ID.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: { id: { type: 'string', description: 'Task ID' } },
|
|
100
|
+
required: ['id']
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'mcp__taskman_task_create',
|
|
105
|
+
description: 'Create a new task.',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: 'object',
|
|
108
|
+
properties: {
|
|
109
|
+
title: { type: 'string', description: 'Task title' },
|
|
110
|
+
content: { type: 'string', description: 'Task content' },
|
|
111
|
+
due_date: { type: 'string', description: 'Due date (YYYY-MM-DD)' },
|
|
112
|
+
reminder: { type: 'string', description: 'Reminder time (YYYY-MM-DD HH:MM)' }
|
|
113
|
+
},
|
|
114
|
+
required: ['title']
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'mcp__taskman_task_toggle',
|
|
119
|
+
description: 'Toggle task completion status.',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: { id: { type: 'string', description: 'Task ID' } },
|
|
123
|
+
required: ['id']
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'mcp__taskman_task_update_status',
|
|
128
|
+
description: 'Update task status (pending, in_progress, completed, archived).',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
id: { type: 'string', description: 'Task ID' },
|
|
133
|
+
status: { type: 'string', description: 'New status', enum: ['pending', 'in_progress', 'completed', 'archived'] }
|
|
134
|
+
},
|
|
135
|
+
required: ['id', 'status']
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
name: 'mcp__taskman_task_add_agent_note',
|
|
140
|
+
description: 'Add an agent note to a task (appended with [AgentX] prefix). Does not modify Connor original content.',
|
|
141
|
+
inputSchema: {
|
|
142
|
+
type: 'object',
|
|
143
|
+
properties: {
|
|
144
|
+
id: { type: 'string', description: 'Task ID' },
|
|
145
|
+
note: { type: 'string', description: 'Note to add' }
|
|
146
|
+
},
|
|
147
|
+
required: ['id', 'note']
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'mcp__taskman_task_delete',
|
|
152
|
+
description: 'Delete a task.',
|
|
153
|
+
inputSchema: {
|
|
154
|
+
type: 'object',
|
|
155
|
+
properties: { id: { type: 'string', description: 'Task ID' } },
|
|
156
|
+
required: ['id']
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'mcp__taskman_subtask_create',
|
|
161
|
+
description: 'Add a subtask to a task.',
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
task_id: { type: 'string', description: 'Parent task ID' },
|
|
166
|
+
title: { type: 'string', description: 'Subtask title' },
|
|
167
|
+
content: { type: 'string', description: 'Subtask content/details' }
|
|
168
|
+
},
|
|
169
|
+
required: ['task_id', 'title']
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'mcp__taskman_subtask_update_status',
|
|
174
|
+
description: 'Update subtask status.',
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
properties: {
|
|
178
|
+
task_id: { type: 'string', description: 'Parent task ID' },
|
|
179
|
+
subtask_id: { type: 'string', description: 'Subtask ID' },
|
|
180
|
+
status: { type: 'string', description: 'New status', enum: ['pending', 'in_progress', 'completed'] }
|
|
181
|
+
},
|
|
182
|
+
required: ['task_id', 'subtask_id', 'status']
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'mcp__taskman_subtask_delete',
|
|
187
|
+
description: 'Delete a subtask.',
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: 'object',
|
|
190
|
+
properties: {
|
|
191
|
+
task_id: { type: 'string', description: 'Parent task ID' },
|
|
192
|
+
subtask_id: { type: 'string', description: 'Subtask ID' }
|
|
193
|
+
},
|
|
194
|
+
required: ['task_id', 'subtask_id']
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'mcp__taskman_idea_list',
|
|
199
|
+
description: 'List all ideas. Optionally filter by tag.',
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: { tag: { type: 'string', description: 'Filter by tag (optional)' } }
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'mcp__taskman_idea_get',
|
|
207
|
+
description: 'Get details of a specific idea by ID.',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
type: 'object',
|
|
210
|
+
properties: { id: { type: 'string', description: 'Idea ID' } },
|
|
211
|
+
required: ['id']
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'mcp__taskman_idea_create',
|
|
216
|
+
description: 'Create a new idea.',
|
|
217
|
+
inputSchema: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
title: { type: 'string', description: 'Idea title' },
|
|
221
|
+
content: { type: 'string', description: 'Idea content' },
|
|
222
|
+
tags: { type: 'string', description: 'Comma-separated tags' }
|
|
223
|
+
},
|
|
224
|
+
required: ['title']
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'mcp__taskman_idea_add_agent_note',
|
|
229
|
+
description: 'Add an agent note to an idea (appended with [AgentX] prefix). Does not modify Connor original content.',
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
id: { type: 'string', description: 'Idea ID' },
|
|
234
|
+
note: { type: 'string', description: 'Note to add' }
|
|
235
|
+
},
|
|
236
|
+
required: ['id', 'note']
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'mcp__taskman_idea_delete',
|
|
241
|
+
description: 'Delete an idea.',
|
|
242
|
+
inputSchema: {
|
|
243
|
+
type: 'object',
|
|
244
|
+
properties: { id: { type: 'string', description: 'Idea ID' } },
|
|
245
|
+
required: ['id']
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}));
|
|
250
|
+
|
|
251
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
252
|
+
const { name, arguments: args } = request.params;
|
|
253
|
+
const store = loadStore();
|
|
254
|
+
|
|
255
|
+
switch (name) {
|
|
256
|
+
case 'mcp__taskman_task_list': {
|
|
257
|
+
let tasks = store.tasks || [];
|
|
258
|
+
if (args?.filter === 'pending') tasks = tasks.filter(t => t.status !== 'completed' && t.status !== 'archived');
|
|
259
|
+
else if (args?.filter === 'completed') tasks = tasks.filter(t => t.status === 'completed' || t.status === 'archived');
|
|
260
|
+
|
|
261
|
+
const lines = [`Found ${tasks.length} tasks:\n`];
|
|
262
|
+
for (const t of tasks) {
|
|
263
|
+
const status = getStatusIcon(t.status);
|
|
264
|
+
const due = t.due_date ? ` (due: ${t.due_date.split('T')[0]})` : '';
|
|
265
|
+
const progress = getProgress(t);
|
|
266
|
+
lines.push(`${status} [${t.id}] ${t.title}${due}${progress}`);
|
|
267
|
+
}
|
|
268
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case 'mcp__taskman_task_get': {
|
|
272
|
+
const task = (store.tasks || []).find(t => t.id === args.id);
|
|
273
|
+
if (!task) return { content: [{ type: 'text', text: `Task not found: ${args.id}` }], isError: true };
|
|
274
|
+
|
|
275
|
+
const due = task.due_date ? task.due_date.split('T')[0] : 'None';
|
|
276
|
+
const reminder = task.reminders?.length ? task.reminders[0].time : 'None';
|
|
277
|
+
|
|
278
|
+
let subtaskInfo = '';
|
|
279
|
+
if (task.subtasks && task.subtasks.length > 0) {
|
|
280
|
+
subtaskInfo = `\nSubtasks (${task.subtasks.length}):`;
|
|
281
|
+
for (const st of task.subtasks) {
|
|
282
|
+
subtaskInfo += `\n ${getStatusIcon(st.status)} [${st.id}] ${st.title}`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const text = `Task: ${task.title}\nID: ${task.id}\nStatus: ${task.status}\nDue: ${due}\nReminder: ${reminder}\nContent:\n${task.content || ''}${subtaskInfo}\nCreated: ${task.created_at}\nUpdated: ${task.updated_at}`;
|
|
287
|
+
return { content: [{ type: 'text', text }] };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
case 'mcp__taskman_task_create': {
|
|
291
|
+
if (!args?.title) return { content: [{ type: 'text', text: 'Title is required' }], isError: true };
|
|
292
|
+
|
|
293
|
+
const now = new Date().toISOString();
|
|
294
|
+
const task = {
|
|
295
|
+
id: generateId(),
|
|
296
|
+
title: args.title,
|
|
297
|
+
content: args.content || '',
|
|
298
|
+
status: 'pending',
|
|
299
|
+
subtasks: [],
|
|
300
|
+
due_date: args.due_date || null,
|
|
301
|
+
reminders: args.reminder ? [{ time: args.reminder, notified: false }] : [],
|
|
302
|
+
created_at: now,
|
|
303
|
+
updated_at: now
|
|
304
|
+
};
|
|
305
|
+
store.tasks = store.tasks || [];
|
|
306
|
+
store.tasks.push(task);
|
|
307
|
+
saveStore(store);
|
|
308
|
+
return { content: [{ type: 'text', text: `Task created: ${args.title}` }] };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case 'mcp__taskman_task_toggle': {
|
|
312
|
+
const idx = (store.tasks || []).findIndex(t => t.id === args.id);
|
|
313
|
+
if (idx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.id}` }], isError: true };
|
|
314
|
+
|
|
315
|
+
store.tasks[idx].status = store.tasks[idx].status === 'completed' ? 'pending' : 'completed';
|
|
316
|
+
store.tasks[idx].updated_at = new Date().toISOString();
|
|
317
|
+
saveStore(store);
|
|
318
|
+
return { content: [{ type: 'text', text: `Task '${store.tasks[idx].title}' marked as ${store.tasks[idx].status}` }] };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'mcp__taskman_task_update_status': {
|
|
322
|
+
const idx = (store.tasks || []).findIndex(t => t.id === args.id);
|
|
323
|
+
if (idx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.id}` }], isError: true };
|
|
324
|
+
|
|
325
|
+
const validStatuses = ['pending', 'in_progress', 'completed', 'archived'];
|
|
326
|
+
if (!validStatuses.includes(args.status)) {
|
|
327
|
+
return { content: [{ type: 'text', text: `Invalid status: ${args.status}` }], isError: true };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
store.tasks[idx].status = args.status;
|
|
331
|
+
store.tasks[idx].updated_at = new Date().toISOString();
|
|
332
|
+
saveStore(store);
|
|
333
|
+
return { content: [{ type: 'text', text: `Task '${store.tasks[idx].title}' status updated to ${args.status}` }] };
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
case 'mcp__taskman_task_add_agent_note': {
|
|
337
|
+
const idx = (store.tasks || []).findIndex(t => t.id === args.id);
|
|
338
|
+
if (idx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.id}` }], isError: true };
|
|
339
|
+
|
|
340
|
+
const timestamp = new Date().toISOString().split('T')[0] + ' ' + new Date().toTimeString().slice(0, 5);
|
|
341
|
+
const agentNote = `\n\n[AgentX ${timestamp}]\n${args.note}`;
|
|
342
|
+
store.tasks[idx].content = (store.tasks[idx].content || '') + agentNote;
|
|
343
|
+
store.tasks[idx].updated_at = new Date().toISOString();
|
|
344
|
+
saveStore(store);
|
|
345
|
+
return { content: [{ type: 'text', text: `Agent note added to task '${store.tasks[idx].title}'` }] };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
case 'mcp__taskman_task_delete': {
|
|
349
|
+
const idx = (store.tasks || []).findIndex(t => t.id === args.id);
|
|
350
|
+
if (idx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.id}` }], isError: true };
|
|
351
|
+
|
|
352
|
+
const title = store.tasks[idx].title;
|
|
353
|
+
store.tasks.splice(idx, 1);
|
|
354
|
+
saveStore(store);
|
|
355
|
+
return { content: [{ type: 'text', text: `Task '${title}' deleted` }] };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
case 'mcp__taskman_subtask_create': {
|
|
359
|
+
if (!args?.title) return { content: [{ type: 'text', text: 'Title is required' }], isError: true };
|
|
360
|
+
|
|
361
|
+
const taskIdx = (store.tasks || []).findIndex(t => t.id === args.task_id);
|
|
362
|
+
if (taskIdx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.task_id}` }], isError: true };
|
|
363
|
+
|
|
364
|
+
const now = new Date().toISOString();
|
|
365
|
+
const subtask = {
|
|
366
|
+
id: generateId(),
|
|
367
|
+
title: args.title,
|
|
368
|
+
content: args.content || '',
|
|
369
|
+
status: 'pending',
|
|
370
|
+
order: (store.tasks[taskIdx].subtasks || []).length,
|
|
371
|
+
created_at: now,
|
|
372
|
+
updated_at: now
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
store.tasks[taskIdx].subtasks = store.tasks[taskIdx].subtasks || [];
|
|
376
|
+
store.tasks[taskIdx].subtasks.push(subtask);
|
|
377
|
+
store.tasks[taskIdx].updated_at = now;
|
|
378
|
+
saveStore(store);
|
|
379
|
+
return { content: [{ type: 'text', text: `Subtask '${args.title}' added to task '${store.tasks[taskIdx].title}'` }] };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
case 'mcp__taskman_subtask_update_status': {
|
|
383
|
+
const taskIdx = (store.tasks || []).findIndex(t => t.id === args.task_id);
|
|
384
|
+
if (taskIdx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.task_id}` }], isError: true };
|
|
385
|
+
|
|
386
|
+
const subtaskIdx = (store.tasks[taskIdx].subtasks || []).findIndex(s => s.id === args.subtask_id);
|
|
387
|
+
if (subtaskIdx === -1) return { content: [{ type: 'text', text: `Subtask not found: ${args.subtask_id}` }], isError: true };
|
|
388
|
+
|
|
389
|
+
const validStatuses = ['pending', 'in_progress', 'completed'];
|
|
390
|
+
if (!validStatuses.includes(args.status)) {
|
|
391
|
+
return { content: [{ type: 'text', text: `Invalid status: ${args.status}` }], isError: true };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
store.tasks[taskIdx].subtasks[subtaskIdx].status = args.status;
|
|
395
|
+
store.tasks[taskIdx].subtasks[subtaskIdx].updated_at = new Date().toISOString();
|
|
396
|
+
store.tasks[taskIdx].updated_at = new Date().toISOString();
|
|
397
|
+
saveStore(store);
|
|
398
|
+
return { content: [{ type: 'text', text: `Subtask '${store.tasks[taskIdx].subtasks[subtaskIdx].title}' status updated to ${args.status}` }] };
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
case 'mcp__taskman_subtask_delete': {
|
|
402
|
+
const taskIdx = (store.tasks || []).findIndex(t => t.id === args.task_id);
|
|
403
|
+
if (taskIdx === -1) return { content: [{ type: 'text', text: `Task not found: ${args.task_id}` }], isError: true };
|
|
404
|
+
|
|
405
|
+
const subtaskIdx = (store.tasks[taskIdx].subtasks || []).findIndex(s => s.id === args.subtask_id);
|
|
406
|
+
if (subtaskIdx === -1) return { content: [{ type: 'text', text: `Subtask not found: ${args.subtask_id}` }], isError: true };
|
|
407
|
+
|
|
408
|
+
const title = store.tasks[taskIdx].subtasks[subtaskIdx].title;
|
|
409
|
+
store.tasks[taskIdx].subtasks.splice(subtaskIdx, 1);
|
|
410
|
+
store.tasks[taskIdx].updated_at = new Date().toISOString();
|
|
411
|
+
saveStore(store);
|
|
412
|
+
return { content: [{ type: 'text', text: `Subtask '${title}' deleted` }] };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
case 'mcp__taskman_idea_list': {
|
|
416
|
+
let ideas = store.ideas || [];
|
|
417
|
+
if (args?.tag) {
|
|
418
|
+
ideas = ideas.filter(i => (i.tags || []).some(t => t.toLowerCase() === args.tag.toLowerCase()));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const lines = [`Found ${ideas.length} ideas:\n`];
|
|
422
|
+
for (const idea of ideas) {
|
|
423
|
+
const tags = idea.tags?.length ? ` [${idea.tags.join(', ')}]` : '';
|
|
424
|
+
lines.push(`☞ [${idea.id}] ${idea.title}${tags}`);
|
|
425
|
+
}
|
|
426
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
case 'mcp__taskman_idea_get': {
|
|
430
|
+
const idea = (store.ideas || []).find(i => i.id === args.id);
|
|
431
|
+
if (!idea) return { content: [{ type: 'text', text: `Idea not found: ${args.id}` }], isError: true };
|
|
432
|
+
|
|
433
|
+
const tags = idea.tags?.length ? idea.tags.join(', ') : 'None';
|
|
434
|
+
const text = `Idea: ${idea.title}\nID: ${idea.id}\nTags: ${tags}\nContent:\n${idea.content || ''}\nCreated: ${idea.created_at}\nUpdated: ${idea.updated_at}`;
|
|
435
|
+
return { content: [{ type: 'text', text }] };
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
case 'mcp__taskman_idea_create': {
|
|
439
|
+
if (!args?.title) return { content: [{ type: 'text', text: 'Title is required' }], isError: true };
|
|
440
|
+
|
|
441
|
+
const now = new Date().toISOString();
|
|
442
|
+
const tags = args.tags ? args.tags.split(',').map(t => t.trim()).filter(t => t) : [];
|
|
443
|
+
const idea = {
|
|
444
|
+
id: generateId(),
|
|
445
|
+
title: args.title,
|
|
446
|
+
content: args.content || '',
|
|
447
|
+
tags,
|
|
448
|
+
status: 'pending',
|
|
449
|
+
subtasks: [],
|
|
450
|
+
created_at: now,
|
|
451
|
+
updated_at: now
|
|
452
|
+
};
|
|
453
|
+
store.ideas = store.ideas || [];
|
|
454
|
+
store.ideas.push(idea);
|
|
455
|
+
saveStore(store);
|
|
456
|
+
return { content: [{ type: 'text', text: `Idea created: ${args.title}` }] };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
case 'mcp__taskman_idea_add_agent_note': {
|
|
460
|
+
const idx = (store.ideas || []).findIndex(i => i.id === args.id);
|
|
461
|
+
if (idx === -1) return { content: [{ type: 'text', text: `Idea not found: ${args.id}` }], isError: true };
|
|
462
|
+
|
|
463
|
+
const timestamp = new Date().toISOString().split('T')[0] + ' ' + new Date().toTimeString().slice(0, 5);
|
|
464
|
+
const agentNote = `\n\n[AgentX ${timestamp}]\n${args.note}`;
|
|
465
|
+
store.ideas[idx].content = (store.ideas[idx].content || '') + agentNote;
|
|
466
|
+
store.ideas[idx].updated_at = new Date().toISOString();
|
|
467
|
+
saveStore(store);
|
|
468
|
+
return { content: [{ type: 'text', text: `Agent note added to idea '${store.ideas[idx].title}'` }] };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
case 'mcp__taskman_idea_delete': {
|
|
472
|
+
const idx = (store.ideas || []).findIndex(i => i.id === args.id);
|
|
473
|
+
if (idx === -1) return { content: [{ type: 'text', text: `Idea not found: ${args.id}` }], isError: true };
|
|
474
|
+
|
|
475
|
+
const title = store.ideas[idx].title;
|
|
476
|
+
store.ideas.splice(idx, 1);
|
|
477
|
+
saveStore(store);
|
|
478
|
+
return { content: [{ type: 'text', text: `Idea '${title}' deleted` }] };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
default:
|
|
482
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const transport = new StdioServerTransport();
|
|
487
|
+
await server.connect(transport);
|
package/package.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "taskman-mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server for taskman task and idea management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "app.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"taskman-mcp": "./app.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node app.js"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^0.5.0"
|
|
15
|
+
}
|
|
16
|
+
}
|