refine-backlog-mcp 1.0.5 → 1.0.7
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 +11 -0
- package/dist/server.js +15 -6
- package/glama.json +6 -0
- package/package.json +25 -2
- package/Dockerfile +0 -9
- package/server.ts +0 -267
- package/tsconfig.json +0 -15
package/README.md
CHANGED
|
@@ -101,6 +101,17 @@ Once configured, just talk to your AI naturally:
|
|
|
101
101
|
|
|
102
102
|
Get a license key at [refinebacklog.com/pricing](https://refinebacklog.com/pricing)
|
|
103
103
|
|
|
104
|
+
## Prefer automation over chat?
|
|
105
|
+
|
|
106
|
+
If you want to run Refine Backlog in scripts, GitHub Actions, or CI pipelines — without an LLM in the loop — use the CLI instead:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npx refine-backlog-cli "Fix login bug" --gherkin
|
|
110
|
+
cat backlog.txt | npx refine-backlog-cli --user-stories --format json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**CLI repo:** [github.com/DavidNielsen1031/refine-backlog-cli](https://github.com/DavidNielsen1031/refine-backlog-cli)
|
|
114
|
+
|
|
104
115
|
## API Reference
|
|
105
116
|
|
|
106
117
|
Full API docs: [refinebacklog.com/llms.txt](https://refinebacklog.com/llms.txt)
|
package/dist/server.js
CHANGED
|
@@ -11,6 +11,9 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
11
11
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
12
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
13
13
|
const API_BASE = "https://refinebacklog.com";
|
|
14
|
+
// License key from MCP server environment — set REFINE_BACKLOG_KEY in Claude Desktop config.
|
|
15
|
+
// This is the preferred way for MCP users with a paid subscription.
|
|
16
|
+
const ENV_LICENSE_KEY = process.env.REFINE_BACKLOG_KEY ?? null;
|
|
14
17
|
const ESTIMATE_LABELS = {
|
|
15
18
|
XS: "< 1 day",
|
|
16
19
|
S: "1–2 days",
|
|
@@ -50,7 +53,10 @@ const REFINE_TOOL = {
|
|
|
50
53
|
"BEFORE calling this tool, ask the user TWO quick questions if they haven't already specified:\n" +
|
|
51
54
|
"1. Would you like titles formatted as user stories? (\"As a [user], I want [goal], so that [benefit]\")\n" +
|
|
52
55
|
"2. Would you like acceptance criteria in Gherkin format? (Given/When/Then)\n" +
|
|
53
|
-
"Set useUserStories and useGherkin accordingly based on their answers. Both default to false
|
|
56
|
+
"Set useUserStories and useGherkin accordingly based on their answers. Both default to false.\n\n" +
|
|
57
|
+
"LICENSE KEY: For unlimited requests and higher item limits, set REFINE_BACKLOG_KEY in your MCP server " +
|
|
58
|
+
"environment config (Claude Desktop → claude_desktop_config.json → env section). " +
|
|
59
|
+
"Get a key at https://refinebacklog.com/pricing",
|
|
54
60
|
inputSchema: {
|
|
55
61
|
type: "object",
|
|
56
62
|
required: ["items"],
|
|
@@ -71,7 +77,8 @@ const REFINE_TOOL = {
|
|
|
71
77
|
licenseKey: {
|
|
72
78
|
type: "string",
|
|
73
79
|
description: "Optional. Refine Backlog license key for Pro or Team tier. " +
|
|
74
|
-
"
|
|
80
|
+
"Preferred: set REFINE_BACKLOG_KEY in your MCP server env config instead of passing inline. " +
|
|
81
|
+
"Get a key at https://refinebacklog.com/pricing. Free tier (5 items, 3 req/day) works without a key.",
|
|
75
82
|
},
|
|
76
83
|
useUserStories: {
|
|
77
84
|
type: "boolean",
|
|
@@ -112,8 +119,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
112
119
|
const headers = {
|
|
113
120
|
"Content-Type": "application/json",
|
|
114
121
|
};
|
|
115
|
-
|
|
116
|
-
|
|
122
|
+
// Inline arg takes precedence; env var is the recommended approach for MCP configs
|
|
123
|
+
const resolvedKey = args.licenseKey ?? ENV_LICENSE_KEY;
|
|
124
|
+
if (resolvedKey) {
|
|
125
|
+
headers["x-license-key"] = resolvedKey;
|
|
117
126
|
}
|
|
118
127
|
const body = {
|
|
119
128
|
items: args.items,
|
|
@@ -125,7 +134,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
125
134
|
if (args.useGherkin !== undefined)
|
|
126
135
|
body.useGherkin = args.useGherkin;
|
|
127
136
|
try {
|
|
128
|
-
const response = await fetch(`${API_BASE}/api/
|
|
137
|
+
const response = await fetch(`${API_BASE}/api/refine`, {
|
|
129
138
|
method: "POST",
|
|
130
139
|
headers,
|
|
131
140
|
body: JSON.stringify(body),
|
|
@@ -136,7 +145,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
136
145
|
return {
|
|
137
146
|
content: [{
|
|
138
147
|
type: "text",
|
|
139
|
-
text: `⚠️ ${msg}\n\n👉 Upgrade at https://refinebacklog.com/pricing
|
|
148
|
+
text: `⚠️ ${msg}\n\n👉 Upgrade at https://refinebacklog.com/pricing\n\nOnce you have a key, add it to your Claude Desktop config:\n{\n "mcpServers": {\n "refine-backlog": {\n "command": "npx",\n "args": ["-y", "refine-backlog-mcp"],\n "env": { "REFINE_BACKLOG_KEY": "your-key-here" }\n }\n }\n}`,
|
|
140
149
|
}],
|
|
141
150
|
isError: true,
|
|
142
151
|
};
|
package/glama.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "refine-backlog-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "MCP server for Refine Backlog — AI-powered backlog refinement",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -20,5 +20,28 @@
|
|
|
20
20
|
"typescript": "^5.0.0",
|
|
21
21
|
"@types/node": "^22.0.0",
|
|
22
22
|
"ts-node": "^10.9.0"
|
|
23
|
-
}
|
|
23
|
+
},
|
|
24
|
+
"author": "David Nielsen <refinebacklog@gmail.com>",
|
|
25
|
+
"homepage": "https://refinebacklog.com",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/DavidNielsen1031/refine-backlog.git",
|
|
29
|
+
"directory": "mcp"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"backlog",
|
|
34
|
+
"refinement",
|
|
35
|
+
"agile",
|
|
36
|
+
"scrum",
|
|
37
|
+
"product-management",
|
|
38
|
+
"ai",
|
|
39
|
+
"claude"
|
|
40
|
+
],
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"glama.json"
|
|
46
|
+
]
|
|
24
47
|
}
|
package/Dockerfile
DELETED
package/server.ts
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Refine Backlog MCP Server
|
|
4
|
-
*
|
|
5
|
-
* Exposes Refine Backlog (https://refinebacklog.com) as a Model Context Protocol tool.
|
|
6
|
-
* Compatible with Claude Desktop, Cursor, and any MCP-capable client.
|
|
7
|
-
*
|
|
8
|
-
* Usage: npx refine-backlog-mcp
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
12
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
|
-
import {
|
|
14
|
-
CallToolRequestSchema,
|
|
15
|
-
ListToolsRequestSchema,
|
|
16
|
-
Tool,
|
|
17
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
18
|
-
|
|
19
|
-
const API_BASE = "https://refinebacklog.com";
|
|
20
|
-
|
|
21
|
-
interface RefinedItem {
|
|
22
|
-
title: string;
|
|
23
|
-
problem: string;
|
|
24
|
-
acceptanceCriteria: string[];
|
|
25
|
-
estimate: "XS" | "S" | "M" | "L" | "XL";
|
|
26
|
-
priority: string;
|
|
27
|
-
tags: string[];
|
|
28
|
-
assumptions?: string[];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
interface RefineResponse {
|
|
32
|
-
items: RefinedItem[];
|
|
33
|
-
_meta: {
|
|
34
|
-
requestId: string;
|
|
35
|
-
model: string;
|
|
36
|
-
inputTokens: number;
|
|
37
|
-
outputTokens: number;
|
|
38
|
-
costUsd: number;
|
|
39
|
-
latencyMs: number;
|
|
40
|
-
tier: string;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const ESTIMATE_LABELS: Record<string, string> = {
|
|
45
|
-
XS: "< 1 day",
|
|
46
|
-
S: "1–2 days",
|
|
47
|
-
M: "3–5 days",
|
|
48
|
-
L: "1–2 weeks",
|
|
49
|
-
XL: "2+ weeks",
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
function formatRefinedItems(items: RefinedItem[]): string {
|
|
53
|
-
return items
|
|
54
|
-
.map((item, i) => {
|
|
55
|
-
const lines = [
|
|
56
|
-
`## ${i + 1}. ${item.title}`,
|
|
57
|
-
``,
|
|
58
|
-
`**Problem:** ${item.problem}`,
|
|
59
|
-
``,
|
|
60
|
-
`**Estimate:** ${item.estimate} (${ESTIMATE_LABELS[item.estimate] ?? item.estimate})`,
|
|
61
|
-
`**Priority:** ${item.priority}`,
|
|
62
|
-
`**Tags:** ${item.tags.join(", ")}`,
|
|
63
|
-
``,
|
|
64
|
-
`**Acceptance Criteria:**`,
|
|
65
|
-
...item.acceptanceCriteria.map((ac) => `- ${ac}`),
|
|
66
|
-
];
|
|
67
|
-
|
|
68
|
-
if (item.assumptions && item.assumptions.length > 0) {
|
|
69
|
-
lines.push(``, `**Assumptions / Open Questions:**`);
|
|
70
|
-
item.assumptions.forEach((a) => lines.push(`- ${a}`));
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return lines.join("\n");
|
|
74
|
-
})
|
|
75
|
-
.join("\n\n---\n\n");
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const REFINE_TOOL: Tool = {
|
|
79
|
-
name: "refine_backlog",
|
|
80
|
-
description:
|
|
81
|
-
"Refine messy backlog items into structured, actionable work items. " +
|
|
82
|
-
"Returns each item with a clean title, problem statement, acceptance criteria, " +
|
|
83
|
-
"T-shirt size estimate (XS/S/M/L/XL), priority with rationale, tags, and optional assumptions. " +
|
|
84
|
-
"Free tier: up to 5 items per request. Pro: 25. Team: 50.\n\n" +
|
|
85
|
-
"BEFORE calling this tool, ask the user TWO quick questions if they haven't already specified:\n" +
|
|
86
|
-
"1. Would you like titles formatted as user stories? (\"As a [user], I want [goal], so that [benefit]\")\n" +
|
|
87
|
-
"2. Would you like acceptance criteria in Gherkin format? (Given/When/Then)\n" +
|
|
88
|
-
"Set useUserStories and useGherkin accordingly based on their answers. Both default to false.",
|
|
89
|
-
inputSchema: {
|
|
90
|
-
type: "object",
|
|
91
|
-
required: ["items"],
|
|
92
|
-
properties: {
|
|
93
|
-
items: {
|
|
94
|
-
type: "array",
|
|
95
|
-
items: { type: "string" },
|
|
96
|
-
minItems: 1,
|
|
97
|
-
maxItems: 50,
|
|
98
|
-
description:
|
|
99
|
-
"Array of raw backlog item strings to refine. " +
|
|
100
|
-
"Each string is a rough description of work to be done.",
|
|
101
|
-
},
|
|
102
|
-
context: {
|
|
103
|
-
type: "string",
|
|
104
|
-
description:
|
|
105
|
-
"Optional project context to improve relevance. " +
|
|
106
|
-
'Example: "B2B SaaS CRM for enterprise sales teams" or "Mobile fitness app for casual runners".',
|
|
107
|
-
},
|
|
108
|
-
licenseKey: {
|
|
109
|
-
type: "string",
|
|
110
|
-
description:
|
|
111
|
-
"Optional. Refine Backlog license key for Pro or Team tier. " +
|
|
112
|
-
"Obtain at https://refinebacklog.com/pricing. Free tier (5 items) works without a key.",
|
|
113
|
-
},
|
|
114
|
-
useUserStories: {
|
|
115
|
-
type: "boolean",
|
|
116
|
-
description:
|
|
117
|
-
'Format titles as user stories: "As a [user], I want [goal], so that [benefit]". Default: false.',
|
|
118
|
-
},
|
|
119
|
-
useGherkin: {
|
|
120
|
-
type: "boolean",
|
|
121
|
-
description:
|
|
122
|
-
"Format acceptance criteria as Gherkin: Given/When/Then. Default: false.",
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
const server = new Server(
|
|
129
|
-
{
|
|
130
|
-
name: "refine-backlog",
|
|
131
|
-
version: "1.0.0",
|
|
132
|
-
},
|
|
133
|
-
{
|
|
134
|
-
capabilities: {
|
|
135
|
-
tools: {},
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
);
|
|
139
|
-
|
|
140
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
141
|
-
tools: [REFINE_TOOL],
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
145
|
-
if (request.params.name !== "refine_backlog") {
|
|
146
|
-
return {
|
|
147
|
-
content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
|
|
148
|
-
isError: true,
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const args = request.params.arguments as {
|
|
153
|
-
items: string[];
|
|
154
|
-
context?: string;
|
|
155
|
-
licenseKey?: string;
|
|
156
|
-
useUserStories?: boolean;
|
|
157
|
-
useGherkin?: boolean;
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
if (!args.items || args.items.length === 0) {
|
|
161
|
-
return {
|
|
162
|
-
content: [{ type: "text", text: "Error: items array is required and must not be empty." }],
|
|
163
|
-
isError: true,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const headers: Record<string, string> = {
|
|
168
|
-
"Content-Type": "application/json",
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
if (args.licenseKey) {
|
|
172
|
-
headers["x-license-key"] = args.licenseKey;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const body: Record<string, unknown> = {
|
|
176
|
-
items: args.items,
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
if (args.context) body.context = args.context;
|
|
180
|
-
if (args.useUserStories !== undefined) body.useUserStories = args.useUserStories;
|
|
181
|
-
if (args.useGherkin !== undefined) body.useGherkin = args.useGherkin;
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const response = await fetch(`${API_BASE}/api/groom`, {
|
|
185
|
-
method: "POST",
|
|
186
|
-
headers,
|
|
187
|
-
body: JSON.stringify(body),
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
if (response.status === 429) {
|
|
191
|
-
const body = await response.json().catch(() => ({})) as { error?: string; upgrade?: string };
|
|
192
|
-
const msg = body.error ?? "Daily request limit reached on the free tier.";
|
|
193
|
-
return {
|
|
194
|
-
content: [{
|
|
195
|
-
type: "text",
|
|
196
|
-
text: `⚠️ ${msg}\n\n👉 Upgrade at https://refinebacklog.com/pricing — add your license key to the Claude Desktop MCP config as: "licenseKey": "your-key-here"`,
|
|
197
|
-
}],
|
|
198
|
-
isError: true,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (response.status === 503) {
|
|
203
|
-
return {
|
|
204
|
-
content: [{ type: "text", text: "Refine Backlog AI service is temporarily unavailable. Please try again in a moment." }],
|
|
205
|
-
isError: true,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (!response.ok) {
|
|
210
|
-
const body = await response.json().catch(() => ({ error: "Unknown error" })) as {
|
|
211
|
-
error?: string;
|
|
212
|
-
upgrade?: string;
|
|
213
|
-
itemsReceived?: number;
|
|
214
|
-
itemsAllowed?: number;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Item limit exceeded — clear upgrade prompt
|
|
218
|
-
if (body.upgrade) {
|
|
219
|
-
return {
|
|
220
|
-
content: [{
|
|
221
|
-
type: "text",
|
|
222
|
-
text: `⚠️ ${body.error}\n\n👉 Upgrade at https://refinebacklog.com/pricing\n\nOnce you have a license key, pass it in your request as the \`licenseKey\` parameter.`,
|
|
223
|
-
}],
|
|
224
|
-
isError: true,
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return {
|
|
229
|
-
content: [{ type: "text", text: `Error from Refine Backlog API: ${body.error ?? response.statusText}` }],
|
|
230
|
-
isError: true,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const data = await response.json() as RefineResponse;
|
|
235
|
-
const formatted = formatRefinedItems(data.items);
|
|
236
|
-
|
|
237
|
-
const meta = data._meta;
|
|
238
|
-
const summary = [
|
|
239
|
-
`\n\n---`,
|
|
240
|
-
`*Refined ${data.items.length} item${data.items.length !== 1 ? "s" : ""} · ` +
|
|
241
|
-
`${meta.latencyMs}ms · ` +
|
|
242
|
-
`Tier: ${meta.tier} · ` +
|
|
243
|
-
`Cost: $${meta.costUsd.toFixed(6)}*`,
|
|
244
|
-
].join("\n");
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
content: [{ type: "text", text: formatted + summary }],
|
|
248
|
-
};
|
|
249
|
-
} catch (err) {
|
|
250
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
251
|
-
return {
|
|
252
|
-
content: [{ type: "text", text: `Failed to reach Refine Backlog API: ${message}` }],
|
|
253
|
-
isError: true,
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
async function main() {
|
|
259
|
-
const transport = new StdioServerTransport();
|
|
260
|
-
await server.connect(transport);
|
|
261
|
-
console.error("Refine Backlog MCP server running on stdio");
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
main().catch((err) => {
|
|
265
|
-
console.error("Fatal error:", err);
|
|
266
|
-
process.exit(1);
|
|
267
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2022",
|
|
4
|
-
"module": "Node16",
|
|
5
|
-
"moduleResolution": "Node16",
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": ".",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"declaration": true
|
|
12
|
-
},
|
|
13
|
-
"include": ["server.ts"],
|
|
14
|
-
"exclude": ["node_modules", "dist"]
|
|
15
|
-
}
|