@damper/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/README.md +73 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +198 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# @damper/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for [Damper](https://usedamper.com) - enables AI agents to manage roadmap tasks.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @damper/mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
### 1. Get API Key
|
|
14
|
+
Damper → Settings → API Keys → Generate
|
|
15
|
+
|
|
16
|
+
### 2. Configure Your AI Agent
|
|
17
|
+
|
|
18
|
+
**Claude Code** (`~/.claude.json`):
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"damper": {
|
|
23
|
+
"command": "damper",
|
|
24
|
+
"env": { "DAMPER_API_KEY": "dmp_..." }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Cursor** (`.cursor/mcp.json`):
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"damper": {
|
|
35
|
+
"command": "npx",
|
|
36
|
+
"args": ["@damper/mcp"],
|
|
37
|
+
"env": { "DAMPER_API_KEY": "dmp_..." }
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Tools
|
|
44
|
+
|
|
45
|
+
| Tool | Description |
|
|
46
|
+
|------|-------------|
|
|
47
|
+
| `list_tasks` | Get roadmap tasks |
|
|
48
|
+
| `get_task` | Task details + feedback |
|
|
49
|
+
| `create_task` | Create task (for TODO imports) |
|
|
50
|
+
| `start_task` | Mark in-progress |
|
|
51
|
+
| `add_note` | Add progress note |
|
|
52
|
+
| `complete_task` | Mark done |
|
|
53
|
+
| `list_feedback` | Browse user feedback |
|
|
54
|
+
| `get_feedback` | Feedback details |
|
|
55
|
+
|
|
56
|
+
## Usage
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
> What tasks are available?
|
|
60
|
+
> Import my TODO.md into Damper
|
|
61
|
+
> Work on the dark mode task
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Environment
|
|
65
|
+
|
|
66
|
+
| Variable | Required | Default |
|
|
67
|
+
|----------|----------|---------|
|
|
68
|
+
| `DAMPER_API_KEY` | Yes | - |
|
|
69
|
+
| `DAMPER_API_URL` | No | https://api.usedamper.com |
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
MIT
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
// Config
|
|
6
|
+
const API_KEY = process.env.DAMPER_API_KEY;
|
|
7
|
+
const API_URL = process.env.DAMPER_API_URL || 'https://api.usedamper.com';
|
|
8
|
+
if (!API_KEY) {
|
|
9
|
+
console.error('DAMPER_API_KEY required. Get one from Damper → Settings → API Keys');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
// API Client
|
|
13
|
+
async function api(method, path, body) {
|
|
14
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
15
|
+
method,
|
|
16
|
+
headers: {
|
|
17
|
+
Authorization: `Bearer ${API_KEY}`,
|
|
18
|
+
'Content-Type': 'application/json',
|
|
19
|
+
},
|
|
20
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
21
|
+
});
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const err = await res.json().catch(() => ({}));
|
|
24
|
+
throw new Error(err.error || `HTTP ${res.status}`);
|
|
25
|
+
}
|
|
26
|
+
return res.json();
|
|
27
|
+
}
|
|
28
|
+
// Server
|
|
29
|
+
const server = new McpServer({
|
|
30
|
+
name: 'damper',
|
|
31
|
+
version: '0.1.0',
|
|
32
|
+
});
|
|
33
|
+
// Tool: List tasks
|
|
34
|
+
server.registerTool('list_tasks', {
|
|
35
|
+
title: 'List Tasks',
|
|
36
|
+
description: 'Get roadmap tasks. Returns planned/in-progress by default.',
|
|
37
|
+
inputSchema: z.object({
|
|
38
|
+
status: z.enum(['planned', 'in_progress', 'done', 'all']).optional(),
|
|
39
|
+
limit: z.number().optional(),
|
|
40
|
+
}),
|
|
41
|
+
}, async ({ status, limit }) => {
|
|
42
|
+
const params = new URLSearchParams();
|
|
43
|
+
if (status)
|
|
44
|
+
params.set('status', status);
|
|
45
|
+
if (limit)
|
|
46
|
+
params.set('limit', String(limit));
|
|
47
|
+
const query = params.toString();
|
|
48
|
+
const data = await api('GET', `/api/agent/tasks${query ? `?${query}` : ''}`);
|
|
49
|
+
if (!data.tasks.length) {
|
|
50
|
+
return { content: [{ type: 'text', text: `No tasks in "${data.project.name}"` }] };
|
|
51
|
+
}
|
|
52
|
+
const lines = data.tasks.map((t) => {
|
|
53
|
+
const p = t.priority === 'high' ? '🔴' : t.priority === 'medium' ? '🟡' : '⚪';
|
|
54
|
+
return `• ${t.id}: ${t.title} [${t.status}] ${p}${t.hasImplementationPlan ? ' 📋' : ''}`;
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
content: [{ type: 'text', text: `Tasks in "${data.project.name}":\n${lines.join('\n')}` }],
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
// Tool: Get task
|
|
61
|
+
server.registerTool('get_task', {
|
|
62
|
+
title: 'Get Task',
|
|
63
|
+
description: 'Get task details including description, plan, and linked feedback.',
|
|
64
|
+
inputSchema: z.object({
|
|
65
|
+
taskId: z.string().describe('Task ID'),
|
|
66
|
+
}),
|
|
67
|
+
}, async ({ taskId }) => {
|
|
68
|
+
const t = await api('GET', `/api/agent/tasks/${taskId}`);
|
|
69
|
+
const parts = [
|
|
70
|
+
`# ${t.title}`,
|
|
71
|
+
`Status: ${t.status} | Score: ${t.voteScore}`,
|
|
72
|
+
];
|
|
73
|
+
if (t.description)
|
|
74
|
+
parts.push(`\n## Description\n${t.description}`);
|
|
75
|
+
if (t.implementationPlan)
|
|
76
|
+
parts.push(`\n## Plan\n${t.implementationPlan}`);
|
|
77
|
+
if (t.agentNotes)
|
|
78
|
+
parts.push(`\n## Notes\n${t.agentNotes}`);
|
|
79
|
+
if (t.feedback.length) {
|
|
80
|
+
parts.push(`\n## Feedback (${t.feedback.length})`);
|
|
81
|
+
t.feedback.forEach((f) => parts.push(`- ${f.title} (${f.voterCount} votes)`));
|
|
82
|
+
}
|
|
83
|
+
return { content: [{ type: 'text', text: parts.join('\n') }] };
|
|
84
|
+
});
|
|
85
|
+
// Tool: Create task
|
|
86
|
+
server.registerTool('create_task', {
|
|
87
|
+
title: 'Create Task',
|
|
88
|
+
description: 'Create a new roadmap task. Use for importing from TODO files.',
|
|
89
|
+
inputSchema: z.object({
|
|
90
|
+
title: z.string(),
|
|
91
|
+
description: z.string().optional(),
|
|
92
|
+
status: z.enum(['planned', 'in_progress']).optional(),
|
|
93
|
+
implementationPlan: z.string().optional(),
|
|
94
|
+
}),
|
|
95
|
+
}, async (args) => {
|
|
96
|
+
const result = await api('POST', '/api/agent/tasks', args);
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: 'text', text: `Created: ${result.id} "${result.title}" [${result.status}]` }],
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
// Tool: Start task
|
|
102
|
+
server.registerTool('start_task', {
|
|
103
|
+
title: 'Start Task',
|
|
104
|
+
description: 'Mark task as in-progress. Call before working on it.',
|
|
105
|
+
inputSchema: z.object({
|
|
106
|
+
taskId: z.string(),
|
|
107
|
+
}),
|
|
108
|
+
}, async ({ taskId }) => {
|
|
109
|
+
const result = await api('POST', `/api/agent/tasks/${taskId}/start`);
|
|
110
|
+
return { content: [{ type: 'text', text: `Started ${result.id}: ${result.message}` }] };
|
|
111
|
+
});
|
|
112
|
+
// Tool: Add note
|
|
113
|
+
server.registerTool('add_note', {
|
|
114
|
+
title: 'Add Note',
|
|
115
|
+
description: 'Add progress note to task.',
|
|
116
|
+
inputSchema: z.object({
|
|
117
|
+
taskId: z.string(),
|
|
118
|
+
note: z.string(),
|
|
119
|
+
}),
|
|
120
|
+
}, async ({ taskId, note }) => {
|
|
121
|
+
await api('POST', `/api/agent/tasks/${taskId}/notes`, { note });
|
|
122
|
+
return { content: [{ type: 'text', text: `Note added to ${taskId}` }] };
|
|
123
|
+
});
|
|
124
|
+
// Tool: Complete task
|
|
125
|
+
server.registerTool('complete_task', {
|
|
126
|
+
title: 'Complete Task',
|
|
127
|
+
description: 'Mark task done with summary.',
|
|
128
|
+
inputSchema: z.object({
|
|
129
|
+
taskId: z.string(),
|
|
130
|
+
summary: z.string().describe('What was implemented'),
|
|
131
|
+
}),
|
|
132
|
+
}, async ({ taskId, summary }) => {
|
|
133
|
+
const result = await api('POST', `/api/agent/tasks/${taskId}/complete`, { summary });
|
|
134
|
+
return { content: [{ type: 'text', text: `Completed ${result.id}` }] };
|
|
135
|
+
});
|
|
136
|
+
// Tool: List feedback
|
|
137
|
+
server.registerTool('list_feedback', {
|
|
138
|
+
title: 'List Feedback',
|
|
139
|
+
description: 'Get user feedback. Useful for understanding user needs.',
|
|
140
|
+
inputSchema: z.object({
|
|
141
|
+
status: z.enum(['new', 'under_review', 'planned', 'in_progress', 'done', 'closed']).optional(),
|
|
142
|
+
limit: z.number().optional(),
|
|
143
|
+
}),
|
|
144
|
+
}, async ({ status, limit }) => {
|
|
145
|
+
const params = new URLSearchParams();
|
|
146
|
+
if (status)
|
|
147
|
+
params.set('status', status);
|
|
148
|
+
if (limit)
|
|
149
|
+
params.set('limit', String(limit));
|
|
150
|
+
const query = params.toString();
|
|
151
|
+
const data = await api('GET', `/api/agent/feedback${query ? `?${query}` : ''}`);
|
|
152
|
+
if (!data.feedback.length) {
|
|
153
|
+
return { content: [{ type: 'text', text: 'No feedback found' }] };
|
|
154
|
+
}
|
|
155
|
+
const lines = data.feedback.map((f) => {
|
|
156
|
+
const link = f.linkedTaskId ? ` → ${f.linkedTaskId}` : '';
|
|
157
|
+
return `• ${f.id}: ${f.title} [${f.type}] (${f.voterCount} votes)${link}`;
|
|
158
|
+
});
|
|
159
|
+
return { content: [{ type: 'text', text: `Feedback:\n${lines.join('\n')}` }] };
|
|
160
|
+
});
|
|
161
|
+
// Tool: Get feedback
|
|
162
|
+
server.registerTool('get_feedback', {
|
|
163
|
+
title: 'Get Feedback',
|
|
164
|
+
description: 'Get feedback details with votes and comments.',
|
|
165
|
+
inputSchema: z.object({
|
|
166
|
+
feedbackId: z.string(),
|
|
167
|
+
}),
|
|
168
|
+
}, async ({ feedbackId }) => {
|
|
169
|
+
const f = await api('GET', `/api/agent/feedback/${feedbackId}`);
|
|
170
|
+
const parts = [
|
|
171
|
+
`# ${f.title}`,
|
|
172
|
+
`Type: ${f.type} | Status: ${f.status} | Score: ${f.voteScore}`,
|
|
173
|
+
f.linkedTaskId ? `Linked: ${f.linkedTaskId}` : '',
|
|
174
|
+
`\n${f.description}`,
|
|
175
|
+
].filter(Boolean);
|
|
176
|
+
if (f.voters.length) {
|
|
177
|
+
parts.push(`\n## Voters (${f.voters.length})`);
|
|
178
|
+
f.voters.slice(0, 5).forEach((v) => parts.push(`- ${v.email} (${v.plan})`));
|
|
179
|
+
if (f.voters.length > 5)
|
|
180
|
+
parts.push(`... and ${f.voters.length - 5} more`);
|
|
181
|
+
}
|
|
182
|
+
if (f.comments.length) {
|
|
183
|
+
parts.push(`\n## Comments (${f.comments.length})`);
|
|
184
|
+
f.comments.slice(0, 3).forEach((c) => parts.push(`**${c.author}**: ${c.body}`));
|
|
185
|
+
if (f.comments.length > 3)
|
|
186
|
+
parts.push(`... and ${f.comments.length - 3} more`);
|
|
187
|
+
}
|
|
188
|
+
return { content: [{ type: 'text', text: parts.join('\n') }] };
|
|
189
|
+
});
|
|
190
|
+
// Start
|
|
191
|
+
async function main() {
|
|
192
|
+
const transport = new StdioServerTransport();
|
|
193
|
+
await server.connect(transport);
|
|
194
|
+
}
|
|
195
|
+
main().catch((e) => {
|
|
196
|
+
console.error('Fatal:', e);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@damper/mcp",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "MCP server for Damper task management",
|
|
5
|
+
"author": "Damper <hello@usedamper.com>",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/usedamper/damper.git",
|
|
9
|
+
"directory": "packages/mcp"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://usedamper.com",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"damper": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": ["dist"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsx src/index.ts",
|
|
22
|
+
"prepublishOnly": "bun run build"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.25.0",
|
|
26
|
+
"zod": "^3.24.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^22.0.0",
|
|
30
|
+
"tsx": "^4.0.0",
|
|
31
|
+
"typescript": "^5.3.0"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18"
|
|
35
|
+
},
|
|
36
|
+
"keywords": ["mcp", "damper", "ai", "task-management"],
|
|
37
|
+
"license": "MIT"
|
|
38
|
+
}
|