@damper/mcp 0.1.1 → 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 +234 -18
- package/package.json +10 -3
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();
|
|
@@ -30,31 +36,94 @@ const server = new McpServer({
|
|
|
30
36
|
name: 'damper',
|
|
31
37
|
version: '0.1.0',
|
|
32
38
|
});
|
|
39
|
+
// Output schemas
|
|
40
|
+
const TaskSummarySchema = z.object({
|
|
41
|
+
id: z.string(),
|
|
42
|
+
title: z.string(),
|
|
43
|
+
type: z.string(),
|
|
44
|
+
status: z.string(),
|
|
45
|
+
priority: z.string(),
|
|
46
|
+
feedbackCount: z.number(),
|
|
47
|
+
hasImplementationPlan: z.boolean(),
|
|
48
|
+
});
|
|
49
|
+
const TaskDetailSchema = z.object({
|
|
50
|
+
id: z.string(),
|
|
51
|
+
title: z.string(),
|
|
52
|
+
description: z.string().optional(),
|
|
53
|
+
type: z.string(),
|
|
54
|
+
implementationPlan: z.string().optional(),
|
|
55
|
+
status: z.string(),
|
|
56
|
+
voteScore: z.number(),
|
|
57
|
+
agentNotes: z.string().optional(),
|
|
58
|
+
feedback: z.array(z.object({
|
|
59
|
+
id: z.string(),
|
|
60
|
+
title: z.string(),
|
|
61
|
+
description: z.string(),
|
|
62
|
+
voterCount: z.number(),
|
|
63
|
+
})),
|
|
64
|
+
});
|
|
65
|
+
const FeedbackSummarySchema = z.object({
|
|
66
|
+
id: z.string(),
|
|
67
|
+
title: z.string(),
|
|
68
|
+
type: z.string(),
|
|
69
|
+
voterCount: z.number(),
|
|
70
|
+
linkedTaskId: z.string().optional(),
|
|
71
|
+
});
|
|
72
|
+
const FeedbackDetailSchema = z.object({
|
|
73
|
+
id: z.string(),
|
|
74
|
+
title: z.string(),
|
|
75
|
+
description: z.string(),
|
|
76
|
+
type: z.string(),
|
|
77
|
+
status: z.string(),
|
|
78
|
+
voteScore: z.number(),
|
|
79
|
+
linkedTaskId: z.string().optional(),
|
|
80
|
+
voters: z.array(z.object({ email: z.string(), plan: z.string() })),
|
|
81
|
+
comments: z.array(z.object({ author: z.string(), body: z.string() })),
|
|
82
|
+
});
|
|
33
83
|
// Tool: List tasks
|
|
34
84
|
server.registerTool('list_tasks', {
|
|
35
85
|
title: 'List Tasks',
|
|
36
|
-
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.',
|
|
37
88
|
inputSchema: z.object({
|
|
38
89
|
status: z.enum(['planned', 'in_progress', 'done', 'all']).optional(),
|
|
90
|
+
type: z.enum(['bug', 'feature', 'improvement', 'task']).optional().describe('Filter by task type'),
|
|
39
91
|
limit: z.number().optional(),
|
|
40
92
|
}),
|
|
41
|
-
|
|
93
|
+
outputSchema: z.object({
|
|
94
|
+
project: z.string(),
|
|
95
|
+
tasks: z.array(TaskSummarySchema),
|
|
96
|
+
}),
|
|
97
|
+
annotations: {
|
|
98
|
+
readOnlyHint: true,
|
|
99
|
+
destructiveHint: false,
|
|
100
|
+
idempotentHint: true,
|
|
101
|
+
openWorldHint: false,
|
|
102
|
+
},
|
|
103
|
+
}, async ({ status, type, limit }) => {
|
|
42
104
|
const params = new URLSearchParams();
|
|
43
105
|
if (status)
|
|
44
106
|
params.set('status', status);
|
|
107
|
+
if (type)
|
|
108
|
+
params.set('type', type);
|
|
45
109
|
if (limit)
|
|
46
110
|
params.set('limit', String(limit));
|
|
47
111
|
const query = params.toString();
|
|
48
112
|
const data = await api('GET', `/api/agent/tasks${query ? `?${query}` : ''}`);
|
|
49
113
|
if (!data.tasks.length) {
|
|
50
|
-
return {
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: `No tasks in "${data.project.name}"${type ? ` (type: ${type})` : ''}` }],
|
|
116
|
+
structuredContent: { project: data.project.name, tasks: [] },
|
|
117
|
+
};
|
|
51
118
|
}
|
|
52
119
|
const lines = data.tasks.map((t) => {
|
|
53
120
|
const p = t.priority === 'high' ? '🔴' : t.priority === 'medium' ? '🟡' : '⚪';
|
|
54
|
-
|
|
121
|
+
const typeIcon = t.type === 'bug' ? '🐛' : t.type === 'feature' ? '✨' : t.type === 'improvement' ? '💡' : '📌';
|
|
122
|
+
return `• ${t.id}: ${typeIcon} ${t.title} [${t.status}] ${p}${t.hasImplementationPlan ? ' 📋' : ''}`;
|
|
55
123
|
});
|
|
56
124
|
return {
|
|
57
125
|
content: [{ type: 'text', text: `Tasks in "${data.project.name}":\n${lines.join('\n')}` }],
|
|
126
|
+
structuredContent: { project: data.project.name, tasks: data.tasks },
|
|
58
127
|
};
|
|
59
128
|
});
|
|
60
129
|
// Tool: Get task
|
|
@@ -64,11 +133,19 @@ server.registerTool('get_task', {
|
|
|
64
133
|
inputSchema: z.object({
|
|
65
134
|
taskId: z.string().describe('Task ID'),
|
|
66
135
|
}),
|
|
136
|
+
outputSchema: TaskDetailSchema,
|
|
137
|
+
annotations: {
|
|
138
|
+
readOnlyHint: true,
|
|
139
|
+
destructiveHint: false,
|
|
140
|
+
idempotentHint: true,
|
|
141
|
+
openWorldHint: false,
|
|
142
|
+
},
|
|
67
143
|
}, async ({ taskId }) => {
|
|
68
144
|
const t = await api('GET', `/api/agent/tasks/${taskId}`);
|
|
145
|
+
const typeIcon = t.type === 'bug' ? '🐛' : t.type === 'feature' ? '✨' : t.type === 'improvement' ? '💡' : '📌';
|
|
69
146
|
const parts = [
|
|
70
|
-
`# ${t.title}`,
|
|
71
|
-
`Status: ${t.status} | Score: ${t.voteScore}`,
|
|
147
|
+
`# ${typeIcon} ${t.title}`,
|
|
148
|
+
`Type: ${t.type} | Status: ${t.status} | Score: ${t.voteScore}`,
|
|
72
149
|
];
|
|
73
150
|
if (t.description)
|
|
74
151
|
parts.push(`\n## Description\n${t.description}`);
|
|
@@ -80,34 +157,96 @@ server.registerTool('get_task', {
|
|
|
80
157
|
parts.push(`\n## Feedback (${t.feedback.length})`);
|
|
81
158
|
t.feedback.forEach((f) => parts.push(`- ${f.title} (${f.voterCount} votes)`));
|
|
82
159
|
}
|
|
83
|
-
return {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: 'text', text: parts.join('\n') }],
|
|
162
|
+
structuredContent: t,
|
|
163
|
+
};
|
|
84
164
|
});
|
|
85
165
|
// Tool: Create task
|
|
86
166
|
server.registerTool('create_task', {
|
|
87
167
|
title: 'Create Task',
|
|
88
|
-
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.',
|
|
89
169
|
inputSchema: z.object({
|
|
90
170
|
title: z.string(),
|
|
91
171
|
description: z.string().optional(),
|
|
172
|
+
type: z.enum(['bug', 'feature', 'improvement', 'task']).optional().describe('Task type (default: feature)'),
|
|
92
173
|
status: z.enum(['planned', 'in_progress']).optional(),
|
|
93
174
|
implementationPlan: z.string().optional(),
|
|
94
175
|
}),
|
|
176
|
+
outputSchema: z.object({
|
|
177
|
+
id: z.string(),
|
|
178
|
+
title: z.string(),
|
|
179
|
+
type: z.string(),
|
|
180
|
+
status: z.string(),
|
|
181
|
+
}),
|
|
182
|
+
annotations: {
|
|
183
|
+
readOnlyHint: false,
|
|
184
|
+
destructiveHint: false,
|
|
185
|
+
idempotentHint: false,
|
|
186
|
+
openWorldHint: false,
|
|
187
|
+
},
|
|
95
188
|
}, async (args) => {
|
|
96
189
|
const result = await api('POST', '/api/agent/tasks', args);
|
|
190
|
+
const typeIcon = result.type === 'bug' ? '🐛' : result.type === 'feature' ? '✨' : result.type === 'improvement' ? '💡' : '📌';
|
|
97
191
|
return {
|
|
98
|
-
content: [{ type: 'text', text: `Created: ${result.id} "${result.title}" [${result.status}]` }],
|
|
192
|
+
content: [{ type: 'text', text: `Created: ${result.id} ${typeIcon} "${result.title}" [${result.status}]` }],
|
|
193
|
+
structuredContent: result,
|
|
99
194
|
};
|
|
100
195
|
});
|
|
101
196
|
// Tool: Start task
|
|
102
197
|
server.registerTool('start_task', {
|
|
103
198
|
title: 'Start Task',
|
|
104
|
-
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).',
|
|
105
201
|
inputSchema: z.object({
|
|
106
202
|
taskId: z.string(),
|
|
203
|
+
force: z.boolean().optional().describe('Take over lock from another agent'),
|
|
107
204
|
}),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
205
|
+
outputSchema: z.object({
|
|
206
|
+
id: z.string(),
|
|
207
|
+
status: z.string(),
|
|
208
|
+
message: z.string(),
|
|
209
|
+
lockedBy: z.string().optional(),
|
|
210
|
+
lockedAt: z.string().optional(),
|
|
211
|
+
}),
|
|
212
|
+
annotations: {
|
|
213
|
+
readOnlyHint: false,
|
|
214
|
+
destructiveHint: false,
|
|
215
|
+
idempotentHint: true,
|
|
216
|
+
openWorldHint: false,
|
|
217
|
+
},
|
|
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
|
+
}
|
|
111
250
|
});
|
|
112
251
|
// Tool: Add note
|
|
113
252
|
server.registerTool('add_note', {
|
|
@@ -117,9 +256,22 @@ server.registerTool('add_note', {
|
|
|
117
256
|
taskId: z.string(),
|
|
118
257
|
note: z.string(),
|
|
119
258
|
}),
|
|
259
|
+
outputSchema: z.object({
|
|
260
|
+
taskId: z.string(),
|
|
261
|
+
success: z.boolean(),
|
|
262
|
+
}),
|
|
263
|
+
annotations: {
|
|
264
|
+
readOnlyHint: false,
|
|
265
|
+
destructiveHint: false,
|
|
266
|
+
idempotentHint: false,
|
|
267
|
+
openWorldHint: false,
|
|
268
|
+
},
|
|
120
269
|
}, async ({ taskId, note }) => {
|
|
121
270
|
await api('POST', `/api/agent/tasks/${taskId}/notes`, { note });
|
|
122
|
-
return {
|
|
271
|
+
return {
|
|
272
|
+
content: [{ type: 'text', text: `Note added to ${taskId}` }],
|
|
273
|
+
structuredContent: { taskId, success: true },
|
|
274
|
+
};
|
|
123
275
|
});
|
|
124
276
|
// Tool: Complete task
|
|
125
277
|
server.registerTool('complete_task', {
|
|
@@ -129,9 +281,48 @@ server.registerTool('complete_task', {
|
|
|
129
281
|
taskId: z.string(),
|
|
130
282
|
summary: z.string().describe('What was implemented'),
|
|
131
283
|
}),
|
|
284
|
+
outputSchema: z.object({
|
|
285
|
+
id: z.string(),
|
|
286
|
+
status: z.string(),
|
|
287
|
+
}),
|
|
288
|
+
annotations: {
|
|
289
|
+
readOnlyHint: false,
|
|
290
|
+
destructiveHint: false,
|
|
291
|
+
idempotentHint: true,
|
|
292
|
+
openWorldHint: false,
|
|
293
|
+
},
|
|
132
294
|
}, async ({ taskId, summary }) => {
|
|
133
295
|
const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary });
|
|
134
|
-
return {
|
|
296
|
+
return {
|
|
297
|
+
content: [{ type: 'text', text: `Completed ${result.id}` }],
|
|
298
|
+
structuredContent: result,
|
|
299
|
+
};
|
|
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
|
+
};
|
|
135
326
|
});
|
|
136
327
|
// Tool: List feedback
|
|
137
328
|
server.registerTool('list_feedback', {
|
|
@@ -141,6 +332,15 @@ server.registerTool('list_feedback', {
|
|
|
141
332
|
status: z.enum(['new', 'under_review', 'planned', 'in_progress', 'done', 'closed']).optional(),
|
|
142
333
|
limit: z.number().optional(),
|
|
143
334
|
}),
|
|
335
|
+
outputSchema: z.object({
|
|
336
|
+
feedback: z.array(FeedbackSummarySchema),
|
|
337
|
+
}),
|
|
338
|
+
annotations: {
|
|
339
|
+
readOnlyHint: true,
|
|
340
|
+
destructiveHint: false,
|
|
341
|
+
idempotentHint: true,
|
|
342
|
+
openWorldHint: false,
|
|
343
|
+
},
|
|
144
344
|
}, async ({ status, limit }) => {
|
|
145
345
|
const params = new URLSearchParams();
|
|
146
346
|
if (status)
|
|
@@ -150,13 +350,19 @@ server.registerTool('list_feedback', {
|
|
|
150
350
|
const query = params.toString();
|
|
151
351
|
const data = await api('GET', `/api/agent/feedback${query ? `?${query}` : ''}`);
|
|
152
352
|
if (!data.feedback.length) {
|
|
153
|
-
return {
|
|
353
|
+
return {
|
|
354
|
+
content: [{ type: 'text', text: 'No feedback found' }],
|
|
355
|
+
structuredContent: { feedback: [] },
|
|
356
|
+
};
|
|
154
357
|
}
|
|
155
358
|
const lines = data.feedback.map((f) => {
|
|
156
359
|
const link = f.linkedTaskId ? ` → ${f.linkedTaskId}` : '';
|
|
157
360
|
return `• ${f.id}: ${f.title} [${f.type}] (${f.voterCount} votes)${link}`;
|
|
158
361
|
});
|
|
159
|
-
return {
|
|
362
|
+
return {
|
|
363
|
+
content: [{ type: 'text', text: `Feedback:\n${lines.join('\n')}` }],
|
|
364
|
+
structuredContent: { feedback: data.feedback },
|
|
365
|
+
};
|
|
160
366
|
});
|
|
161
367
|
// Tool: Get feedback
|
|
162
368
|
server.registerTool('get_feedback', {
|
|
@@ -165,6 +371,13 @@ server.registerTool('get_feedback', {
|
|
|
165
371
|
inputSchema: z.object({
|
|
166
372
|
feedbackId: z.string(),
|
|
167
373
|
}),
|
|
374
|
+
outputSchema: FeedbackDetailSchema,
|
|
375
|
+
annotations: {
|
|
376
|
+
readOnlyHint: true,
|
|
377
|
+
destructiveHint: false,
|
|
378
|
+
idempotentHint: true,
|
|
379
|
+
openWorldHint: false,
|
|
380
|
+
},
|
|
168
381
|
}, async ({ feedbackId }) => {
|
|
169
382
|
const f = await api('GET', `/api/agent/feedback/${feedbackId}`);
|
|
170
383
|
const parts = [
|
|
@@ -185,7 +398,10 @@ server.registerTool('get_feedback', {
|
|
|
185
398
|
if (f.comments.length > 3)
|
|
186
399
|
parts.push(`... and ${f.comments.length - 3} more`);
|
|
187
400
|
}
|
|
188
|
-
return {
|
|
401
|
+
return {
|
|
402
|
+
content: [{ type: 'text', text: parts.join('\n') }],
|
|
403
|
+
structuredContent: f,
|
|
404
|
+
};
|
|
189
405
|
});
|
|
190
406
|
// Start
|
|
191
407
|
async function main() {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@damper/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "MCP server for Damper task management",
|
|
5
5
|
"author": "Damper <hello@usedamper.com>",
|
|
6
6
|
"repository": {
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
},
|
|
16
16
|
"main": "./dist/index.js",
|
|
17
17
|
"types": "./dist/index.d.ts",
|
|
18
|
-
"files": [
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
19
21
|
"scripts": {
|
|
20
22
|
"build": "tsc",
|
|
21
23
|
"dev": "tsx src/index.ts",
|
|
@@ -33,6 +35,11 @@
|
|
|
33
35
|
"engines": {
|
|
34
36
|
"node": ">=18"
|
|
35
37
|
},
|
|
36
|
-
"keywords": [
|
|
38
|
+
"keywords": [
|
|
39
|
+
"mcp",
|
|
40
|
+
"damper",
|
|
41
|
+
"ai",
|
|
42
|
+
"task-management"
|
|
43
|
+
],
|
|
37
44
|
"license": "MIT"
|
|
38
45
|
}
|