@damper/mcp 0.1.2 → 0.1.3
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/dist/index.js +88 -15
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -21,6 +21,12 @@ async function api(method, path, body) {
|
|
|
21
21
|
});
|
|
22
22
|
if (!res.ok) {
|
|
23
23
|
const err = await res.json().catch(() => ({}));
|
|
24
|
+
// Preserve lock info for 409 conflicts
|
|
25
|
+
if (res.status === 409 && err.lockedBy) {
|
|
26
|
+
const lockErr = new Error(err.error || `HTTP ${res.status}`);
|
|
27
|
+
lockErr.lockInfo = err;
|
|
28
|
+
throw lockErr;
|
|
29
|
+
}
|
|
24
30
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
25
31
|
}
|
|
26
32
|
return res.json();
|
|
@@ -34,6 +40,7 @@ const server = new McpServer({
|
|
|
34
40
|
const TaskSummarySchema = z.object({
|
|
35
41
|
id: z.string(),
|
|
36
42
|
title: z.string(),
|
|
43
|
+
type: z.string(),
|
|
37
44
|
status: z.string(),
|
|
38
45
|
priority: z.string(),
|
|
39
46
|
feedbackCount: z.number(),
|
|
@@ -43,6 +50,7 @@ const TaskDetailSchema = z.object({
|
|
|
43
50
|
id: z.string(),
|
|
44
51
|
title: z.string(),
|
|
45
52
|
description: z.string().optional(),
|
|
53
|
+
type: z.string(),
|
|
46
54
|
implementationPlan: z.string().optional(),
|
|
47
55
|
status: z.string(),
|
|
48
56
|
voteScore: z.number(),
|
|
@@ -75,9 +83,11 @@ const FeedbackDetailSchema = z.object({
|
|
|
75
83
|
// Tool: List tasks
|
|
76
84
|
server.registerTool('list_tasks', {
|
|
77
85
|
title: 'List Tasks',
|
|
78
|
-
description: 'Get roadmap tasks. Returns planned/in-progress by default.'
|
|
86
|
+
description: 'Get roadmap tasks. Returns planned/in-progress by default. ' +
|
|
87
|
+
'Filter by type to get bugs, features, etc.',
|
|
79
88
|
inputSchema: z.object({
|
|
80
89
|
status: z.enum(['planned', 'in_progress', 'done', 'all']).optional(),
|
|
90
|
+
type: z.enum(['bug', 'feature', 'improvement', 'task']).optional().describe('Filter by task type'),
|
|
81
91
|
limit: z.number().optional(),
|
|
82
92
|
}),
|
|
83
93
|
outputSchema: z.object({
|
|
@@ -90,23 +100,26 @@ server.registerTool('list_tasks', {
|
|
|
90
100
|
idempotentHint: true,
|
|
91
101
|
openWorldHint: false,
|
|
92
102
|
},
|
|
93
|
-
}, async ({ status, limit }) => {
|
|
103
|
+
}, async ({ status, type, limit }) => {
|
|
94
104
|
const params = new URLSearchParams();
|
|
95
105
|
if (status)
|
|
96
106
|
params.set('status', status);
|
|
107
|
+
if (type)
|
|
108
|
+
params.set('type', type);
|
|
97
109
|
if (limit)
|
|
98
110
|
params.set('limit', String(limit));
|
|
99
111
|
const query = params.toString();
|
|
100
112
|
const data = await api('GET', `/api/agent/tasks${query ? `?${query}` : ''}`);
|
|
101
113
|
if (!data.tasks.length) {
|
|
102
114
|
return {
|
|
103
|
-
content: [{ type: 'text', text: `No tasks in "${data.project.name}"` }],
|
|
115
|
+
content: [{ type: 'text', text: `No tasks in "${data.project.name}"${type ? ` (type: ${type})` : ''}` }],
|
|
104
116
|
structuredContent: { project: data.project.name, tasks: [] },
|
|
105
117
|
};
|
|
106
118
|
}
|
|
107
119
|
const lines = data.tasks.map((t) => {
|
|
108
120
|
const p = t.priority === 'high' ? '🔴' : t.priority === 'medium' ? '🟡' : '⚪';
|
|
109
|
-
|
|
121
|
+
const typeIcon = t.type === 'bug' ? '🐛' : t.type === 'feature' ? '✨' : t.type === 'improvement' ? '💡' : '📌';
|
|
122
|
+
return `• ${t.id}: ${typeIcon} ${t.title} [${t.status}] ${p}${t.hasImplementationPlan ? ' 📋' : ''}`;
|
|
110
123
|
});
|
|
111
124
|
return {
|
|
112
125
|
content: [{ type: 'text', text: `Tasks in "${data.project.name}":\n${lines.join('\n')}` }],
|
|
@@ -129,9 +142,10 @@ server.registerTool('get_task', {
|
|
|
129
142
|
},
|
|
130
143
|
}, async ({ taskId }) => {
|
|
131
144
|
const t = await api('GET', `/api/agent/tasks/${taskId}`);
|
|
145
|
+
const typeIcon = t.type === 'bug' ? '🐛' : t.type === 'feature' ? '✨' : t.type === 'improvement' ? '💡' : '📌';
|
|
132
146
|
const parts = [
|
|
133
|
-
`# ${t.title}`,
|
|
134
|
-
`Status: ${t.status} | Score: ${t.voteScore}`,
|
|
147
|
+
`# ${typeIcon} ${t.title}`,
|
|
148
|
+
`Type: ${t.type} | Status: ${t.status} | Score: ${t.voteScore}`,
|
|
135
149
|
];
|
|
136
150
|
if (t.description)
|
|
137
151
|
parts.push(`\n## Description\n${t.description}`);
|
|
@@ -151,16 +165,18 @@ server.registerTool('get_task', {
|
|
|
151
165
|
// Tool: Create task
|
|
152
166
|
server.registerTool('create_task', {
|
|
153
167
|
title: 'Create Task',
|
|
154
|
-
description: 'Create a new roadmap task. Use for importing from TODO files.',
|
|
168
|
+
description: 'Create a new roadmap task. Use for importing from TODO files or creating work items.',
|
|
155
169
|
inputSchema: z.object({
|
|
156
170
|
title: z.string(),
|
|
157
171
|
description: z.string().optional(),
|
|
172
|
+
type: z.enum(['bug', 'feature', 'improvement', 'task']).optional().describe('Task type (default: feature)'),
|
|
158
173
|
status: z.enum(['planned', 'in_progress']).optional(),
|
|
159
174
|
implementationPlan: z.string().optional(),
|
|
160
175
|
}),
|
|
161
176
|
outputSchema: z.object({
|
|
162
177
|
id: z.string(),
|
|
163
178
|
title: z.string(),
|
|
179
|
+
type: z.string(),
|
|
164
180
|
status: z.string(),
|
|
165
181
|
}),
|
|
166
182
|
annotations: {
|
|
@@ -171,22 +187,27 @@ server.registerTool('create_task', {
|
|
|
171
187
|
},
|
|
172
188
|
}, async (args) => {
|
|
173
189
|
const result = await api('POST', '/api/agent/tasks', args);
|
|
190
|
+
const typeIcon = result.type === 'bug' ? '🐛' : result.type === 'feature' ? '✨' : result.type === 'improvement' ? '💡' : '📌';
|
|
174
191
|
return {
|
|
175
|
-
content: [{ type: 'text', text: `Created: ${result.id} "${result.title}" [${result.status}]` }],
|
|
192
|
+
content: [{ type: 'text', text: `Created: ${result.id} ${typeIcon} "${result.title}" [${result.status}]` }],
|
|
176
193
|
structuredContent: result,
|
|
177
194
|
};
|
|
178
195
|
});
|
|
179
196
|
// Tool: Start task
|
|
180
197
|
server.registerTool('start_task', {
|
|
181
198
|
title: 'Start Task',
|
|
182
|
-
description: '
|
|
199
|
+
description: 'Lock and start a task. Fails if locked by another agent unless force=true. ' +
|
|
200
|
+
'Use force=true to take over a task from another agent (e.g., if they abandoned it).',
|
|
183
201
|
inputSchema: z.object({
|
|
184
202
|
taskId: z.string(),
|
|
203
|
+
force: z.boolean().optional().describe('Take over lock from another agent'),
|
|
185
204
|
}),
|
|
186
205
|
outputSchema: z.object({
|
|
187
206
|
id: z.string(),
|
|
188
207
|
status: z.string(),
|
|
189
208
|
message: z.string(),
|
|
209
|
+
lockedBy: z.string().optional(),
|
|
210
|
+
lockedAt: z.string().optional(),
|
|
190
211
|
}),
|
|
191
212
|
annotations: {
|
|
192
213
|
readOnlyHint: false,
|
|
@@ -194,12 +215,38 @@ server.registerTool('start_task', {
|
|
|
194
215
|
idempotentHint: true,
|
|
195
216
|
openWorldHint: false,
|
|
196
217
|
},
|
|
197
|
-
}, async ({ taskId }) => {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
218
|
+
}, async ({ taskId, force }) => {
|
|
219
|
+
try {
|
|
220
|
+
const result = await api('POST', `/api/agent/tasks/${taskId}/start`, force ? { force: true } : undefined);
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: 'text', text: `Started ${result.id}: ${result.message}` }],
|
|
223
|
+
structuredContent: result,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
const error = err;
|
|
228
|
+
if (error.lockInfo) {
|
|
229
|
+
// Return lock info so AI can decide whether to force takeover
|
|
230
|
+
return {
|
|
231
|
+
content: [
|
|
232
|
+
{
|
|
233
|
+
type: 'text',
|
|
234
|
+
text: `⚠️ Task is locked by "${error.lockInfo.lockedBy}" since ${error.lockInfo.lockedAt}. ` +
|
|
235
|
+
'Use force=true to take over the lock.',
|
|
236
|
+
},
|
|
237
|
+
],
|
|
238
|
+
structuredContent: {
|
|
239
|
+
id: taskId,
|
|
240
|
+
status: 'locked',
|
|
241
|
+
message: error.message,
|
|
242
|
+
lockedBy: error.lockInfo.lockedBy,
|
|
243
|
+
lockedAt: error.lockInfo.lockedAt,
|
|
244
|
+
},
|
|
245
|
+
isError: true,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
203
250
|
});
|
|
204
251
|
// Tool: Add note
|
|
205
252
|
server.registerTool('add_note', {
|
|
@@ -251,6 +298,32 @@ server.registerTool('complete_task', {
|
|
|
251
298
|
structuredContent: result,
|
|
252
299
|
};
|
|
253
300
|
});
|
|
301
|
+
// Tool: Abandon task
|
|
302
|
+
server.registerTool('abandon_task', {
|
|
303
|
+
title: 'Abandon Task',
|
|
304
|
+
description: 'Release lock and return task to planned status. Use when you cannot complete the task ' +
|
|
305
|
+
'or need to stop working on it.',
|
|
306
|
+
inputSchema: z.object({
|
|
307
|
+
taskId: z.string(),
|
|
308
|
+
}),
|
|
309
|
+
outputSchema: z.object({
|
|
310
|
+
id: z.string(),
|
|
311
|
+
status: z.string(),
|
|
312
|
+
message: z.string(),
|
|
313
|
+
}),
|
|
314
|
+
annotations: {
|
|
315
|
+
readOnlyHint: false,
|
|
316
|
+
destructiveHint: false,
|
|
317
|
+
idempotentHint: true,
|
|
318
|
+
openWorldHint: false,
|
|
319
|
+
},
|
|
320
|
+
}, async ({ taskId }) => {
|
|
321
|
+
const result = await api('POST', `/api/agent/tasks/${taskId}/abandon`);
|
|
322
|
+
return {
|
|
323
|
+
content: [{ type: 'text', text: `Abandoned ${result.id}: ${result.message}` }],
|
|
324
|
+
structuredContent: result,
|
|
325
|
+
};
|
|
326
|
+
});
|
|
254
327
|
// Tool: List feedback
|
|
255
328
|
server.registerTool('list_feedback', {
|
|
256
329
|
title: 'List Feedback',
|