refine-backlog-mcp 1.0.0

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 ADDED
@@ -0,0 +1,107 @@
1
+ # Refine Backlog MCP Server
2
+
3
+ Use Refine Backlog directly inside Claude Desktop, Cursor, or any MCP-compatible client.
4
+ Tell your AI to refine your backlog and it calls the API automatically — no copy-paste required.
5
+
6
+ ## What it does
7
+
8
+ Exposes a single tool: `refine_backlog`
9
+
10
+ Give it a list of rough backlog items. Get back structured work items with:
11
+ - Clean, actionable titles
12
+ - Problem statements
13
+ - Acceptance criteria (2-4 per item)
14
+ - T-shirt size estimates (XS/S/M/L/XL)
15
+ - Priorities with rationale
16
+ - Tags
17
+ - Clarifying assumptions (when needed)
18
+
19
+ ## Quick Start
20
+
21
+ ### Option 1: npx (no install)
22
+
23
+ ```bash
24
+ npx refine-backlog-mcp
25
+ ```
26
+
27
+ ### Option 2: Local build
28
+
29
+ ```bash
30
+ cd mcp
31
+ npm install
32
+ npm run build
33
+ node dist/server.js
34
+ ```
35
+
36
+ ## Claude Desktop Setup
37
+
38
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on Mac):
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "refine-backlog": {
44
+ "command": "npx",
45
+ "args": ["refine-backlog-mcp"]
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ With a license key (Pro/Team tier):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "refine-backlog": {
57
+ "command": "npx",
58
+ "args": ["refine-backlog-mcp"],
59
+ "env": {
60
+ "REFINE_LICENSE_KEY": "your-license-key-here"
61
+ }
62
+ }
63
+ }
64
+ }
65
+ ```
66
+
67
+ Restart Claude Desktop. You'll see "refine_backlog" in the tools list.
68
+
69
+ ## Cursor Setup
70
+
71
+ Add to your Cursor MCP config (`~/.cursor/mcp.json`):
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "refine-backlog": {
77
+ "command": "npx",
78
+ "args": ["refine-backlog-mcp"]
79
+ }
80
+ }
81
+ }
82
+ ```
83
+
84
+ ## Usage Examples
85
+
86
+ Once configured, just talk to your AI naturally:
87
+
88
+ > "Refine these backlog items: fix login bug, add CSV export, improve dashboard load time"
89
+
90
+ > "Take these 10 stories and run them through Refine Backlog. Context: we're building a B2B SaaS for HR teams."
91
+
92
+ > "Refine this backlog item as a user story with Gherkin acceptance criteria: users need to reset their password"
93
+
94
+ ## Rate Limits
95
+
96
+ | Tier | Items per request | Price |
97
+ |------|-------------------|-------|
98
+ | Free | 5 | $0 — no key needed |
99
+ | Pro | 25 | $9/month |
100
+ | Team | 50 | $29/month |
101
+
102
+ Get a license key at [refinebacklog.com/pricing](https://refinebacklog.com/pricing)
103
+
104
+ ## API Reference
105
+
106
+ Full API docs: [refinebacklog.com/llms.txt](https://refinebacklog.com/llms.txt)
107
+ OpenAPI spec: [refinebacklog.com/openapi.yaml](https://refinebacklog.com/openapi.yaml)
@@ -0,0 +1,10 @@
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
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,181 @@
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
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
13
+ const API_BASE = "https://refinebacklog.com";
14
+ const ESTIMATE_LABELS = {
15
+ XS: "< 1 day",
16
+ S: "1–2 days",
17
+ M: "3–5 days",
18
+ L: "1–2 weeks",
19
+ XL: "2+ weeks",
20
+ };
21
+ function formatRefinedItems(items) {
22
+ return items
23
+ .map((item, i) => {
24
+ const lines = [
25
+ `## ${i + 1}. ${item.title}`,
26
+ ``,
27
+ `**Problem:** ${item.problem}`,
28
+ ``,
29
+ `**Estimate:** ${item.estimate} (${ESTIMATE_LABELS[item.estimate] ?? item.estimate})`,
30
+ `**Priority:** ${item.priority}`,
31
+ `**Tags:** ${item.tags.join(", ")}`,
32
+ ``,
33
+ `**Acceptance Criteria:**`,
34
+ ...item.acceptanceCriteria.map((ac) => `- ${ac}`),
35
+ ];
36
+ if (item.assumptions && item.assumptions.length > 0) {
37
+ lines.push(``, `**Assumptions / Open Questions:**`);
38
+ item.assumptions.forEach((a) => lines.push(`- ${a}`));
39
+ }
40
+ return lines.join("\n");
41
+ })
42
+ .join("\n\n---\n\n");
43
+ }
44
+ const REFINE_TOOL = {
45
+ name: "refine_backlog",
46
+ description: "Refine messy backlog items into structured, actionable work items. " +
47
+ "Returns each item with a clean title, problem statement, acceptance criteria, " +
48
+ "T-shirt size estimate (XS/S/M/L/XL), priority with rationale, tags, and optional assumptions. " +
49
+ "Free tier: up to 5 items per request. Pro: 25. Team: 50.",
50
+ inputSchema: {
51
+ type: "object",
52
+ required: ["items"],
53
+ properties: {
54
+ items: {
55
+ type: "array",
56
+ items: { type: "string" },
57
+ minItems: 1,
58
+ maxItems: 50,
59
+ description: "Array of raw backlog item strings to refine. " +
60
+ "Each string is a rough description of work to be done.",
61
+ },
62
+ context: {
63
+ type: "string",
64
+ description: "Optional project context to improve relevance. " +
65
+ 'Example: "B2B SaaS CRM for enterprise sales teams" or "Mobile fitness app for casual runners".',
66
+ },
67
+ licenseKey: {
68
+ type: "string",
69
+ description: "Optional. Refine Backlog license key for Pro or Team tier. " +
70
+ "Obtain at https://refinebacklog.com/pricing. Free tier (5 items) works without a key.",
71
+ },
72
+ useUserStories: {
73
+ type: "boolean",
74
+ description: 'Format titles as user stories: "As a [user], I want [goal], so that [benefit]". Default: false.',
75
+ },
76
+ useGherkin: {
77
+ type: "boolean",
78
+ description: "Format acceptance criteria as Gherkin: Given/When/Then. Default: false.",
79
+ },
80
+ },
81
+ },
82
+ };
83
+ const server = new Server({
84
+ name: "refine-backlog",
85
+ version: "1.0.0",
86
+ }, {
87
+ capabilities: {
88
+ tools: {},
89
+ },
90
+ });
91
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
92
+ tools: [REFINE_TOOL],
93
+ }));
94
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
95
+ if (request.params.name !== "refine_backlog") {
96
+ return {
97
+ content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
98
+ isError: true,
99
+ };
100
+ }
101
+ const args = request.params.arguments;
102
+ if (!args.items || args.items.length === 0) {
103
+ return {
104
+ content: [{ type: "text", text: "Error: items array is required and must not be empty." }],
105
+ isError: true,
106
+ };
107
+ }
108
+ const headers = {
109
+ "Content-Type": "application/json",
110
+ };
111
+ if (args.licenseKey) {
112
+ headers["x-license-key"] = args.licenseKey;
113
+ }
114
+ const body = {
115
+ items: args.items,
116
+ };
117
+ if (args.context)
118
+ body.context = args.context;
119
+ if (args.useUserStories !== undefined)
120
+ body.useUserStories = args.useUserStories;
121
+ if (args.useGherkin !== undefined)
122
+ body.useGherkin = args.useGherkin;
123
+ try {
124
+ const response = await fetch(`${API_BASE}/api/groom`, {
125
+ method: "POST",
126
+ headers,
127
+ body: JSON.stringify(body),
128
+ });
129
+ if (response.status === 429) {
130
+ return {
131
+ content: [{
132
+ type: "text",
133
+ text: "Rate limit exceeded. Free tier allows 5 items per request. Upgrade at https://refinebacklog.com/pricing",
134
+ }],
135
+ isError: true,
136
+ };
137
+ }
138
+ if (response.status === 503) {
139
+ return {
140
+ content: [{ type: "text", text: "Refine Backlog AI service is temporarily unavailable. Please try again in a moment." }],
141
+ isError: true,
142
+ };
143
+ }
144
+ if (!response.ok) {
145
+ const error = await response.json().catch(() => ({ error: "Unknown error" }));
146
+ return {
147
+ content: [{ type: "text", text: `Error from Refine Backlog API: ${error.error ?? response.statusText}` }],
148
+ isError: true,
149
+ };
150
+ }
151
+ const data = await response.json();
152
+ const formatted = formatRefinedItems(data.items);
153
+ const meta = data._meta;
154
+ const summary = [
155
+ `\n\n---`,
156
+ `*Refined ${data.items.length} item${data.items.length !== 1 ? "s" : ""} · ` +
157
+ `${meta.latencyMs}ms · ` +
158
+ `Tier: ${meta.tier} · ` +
159
+ `Cost: $${meta.costUsd.toFixed(6)}*`,
160
+ ].join("\n");
161
+ return {
162
+ content: [{ type: "text", text: formatted + summary }],
163
+ };
164
+ }
165
+ catch (err) {
166
+ const message = err instanceof Error ? err.message : String(err);
167
+ return {
168
+ content: [{ type: "text", text: `Failed to reach Refine Backlog API: ${message}` }],
169
+ isError: true,
170
+ };
171
+ }
172
+ });
173
+ async function main() {
174
+ const transport = new StdioServerTransport();
175
+ await server.connect(transport);
176
+ console.error("Refine Backlog MCP server running on stdio");
177
+ }
178
+ main().catch((err) => {
179
+ console.error("Fatal error:", err);
180
+ process.exit(1);
181
+ });
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "refine-backlog-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Refine Backlog — AI-powered backlog refinement",
5
+ "type": "module",
6
+ "main": "server.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "start": "node server.js",
10
+ "dev": "ts-node server.ts"
11
+ },
12
+ "bin": {
13
+ "refine-backlog-mcp": "./server.js"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "^5.0.0",
20
+ "@types/node": "^22.0.0",
21
+ "ts-node": "^10.9.0"
22
+ }
23
+ }
package/server.ts ADDED
@@ -0,0 +1,244 @@
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.",
85
+ inputSchema: {
86
+ type: "object",
87
+ required: ["items"],
88
+ properties: {
89
+ items: {
90
+ type: "array",
91
+ items: { type: "string" },
92
+ minItems: 1,
93
+ maxItems: 50,
94
+ description:
95
+ "Array of raw backlog item strings to refine. " +
96
+ "Each string is a rough description of work to be done.",
97
+ },
98
+ context: {
99
+ type: "string",
100
+ description:
101
+ "Optional project context to improve relevance. " +
102
+ 'Example: "B2B SaaS CRM for enterprise sales teams" or "Mobile fitness app for casual runners".',
103
+ },
104
+ licenseKey: {
105
+ type: "string",
106
+ description:
107
+ "Optional. Refine Backlog license key for Pro or Team tier. " +
108
+ "Obtain at https://refinebacklog.com/pricing. Free tier (5 items) works without a key.",
109
+ },
110
+ useUserStories: {
111
+ type: "boolean",
112
+ description:
113
+ 'Format titles as user stories: "As a [user], I want [goal], so that [benefit]". Default: false.',
114
+ },
115
+ useGherkin: {
116
+ type: "boolean",
117
+ description:
118
+ "Format acceptance criteria as Gherkin: Given/When/Then. Default: false.",
119
+ },
120
+ },
121
+ },
122
+ };
123
+
124
+ const server = new Server(
125
+ {
126
+ name: "refine-backlog",
127
+ version: "1.0.0",
128
+ },
129
+ {
130
+ capabilities: {
131
+ tools: {},
132
+ },
133
+ }
134
+ );
135
+
136
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
137
+ tools: [REFINE_TOOL],
138
+ }));
139
+
140
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
141
+ if (request.params.name !== "refine_backlog") {
142
+ return {
143
+ content: [{ type: "text", text: `Unknown tool: ${request.params.name}` }],
144
+ isError: true,
145
+ };
146
+ }
147
+
148
+ const args = request.params.arguments as {
149
+ items: string[];
150
+ context?: string;
151
+ licenseKey?: string;
152
+ useUserStories?: boolean;
153
+ useGherkin?: boolean;
154
+ };
155
+
156
+ if (!args.items || args.items.length === 0) {
157
+ return {
158
+ content: [{ type: "text", text: "Error: items array is required and must not be empty." }],
159
+ isError: true,
160
+ };
161
+ }
162
+
163
+ const headers: Record<string, string> = {
164
+ "Content-Type": "application/json",
165
+ };
166
+
167
+ if (args.licenseKey) {
168
+ headers["x-license-key"] = args.licenseKey;
169
+ }
170
+
171
+ const body: Record<string, unknown> = {
172
+ items: args.items,
173
+ };
174
+
175
+ if (args.context) body.context = args.context;
176
+ if (args.useUserStories !== undefined) body.useUserStories = args.useUserStories;
177
+ if (args.useGherkin !== undefined) body.useGherkin = args.useGherkin;
178
+
179
+ try {
180
+ const response = await fetch(`${API_BASE}/api/groom`, {
181
+ method: "POST",
182
+ headers,
183
+ body: JSON.stringify(body),
184
+ });
185
+
186
+ if (response.status === 429) {
187
+ return {
188
+ content: [{
189
+ type: "text",
190
+ text: "Rate limit exceeded. Free tier allows 5 items per request. Upgrade at https://refinebacklog.com/pricing",
191
+ }],
192
+ isError: true,
193
+ };
194
+ }
195
+
196
+ if (response.status === 503) {
197
+ return {
198
+ content: [{ type: "text", text: "Refine Backlog AI service is temporarily unavailable. Please try again in a moment." }],
199
+ isError: true,
200
+ };
201
+ }
202
+
203
+ if (!response.ok) {
204
+ const error = await response.json().catch(() => ({ error: "Unknown error" })) as { error?: string };
205
+ return {
206
+ content: [{ type: "text", text: `Error from Refine Backlog API: ${error.error ?? response.statusText}` }],
207
+ isError: true,
208
+ };
209
+ }
210
+
211
+ const data = await response.json() as RefineResponse;
212
+ const formatted = formatRefinedItems(data.items);
213
+
214
+ const meta = data._meta;
215
+ const summary = [
216
+ `\n\n---`,
217
+ `*Refined ${data.items.length} item${data.items.length !== 1 ? "s" : ""} · ` +
218
+ `${meta.latencyMs}ms · ` +
219
+ `Tier: ${meta.tier} · ` +
220
+ `Cost: $${meta.costUsd.toFixed(6)}*`,
221
+ ].join("\n");
222
+
223
+ return {
224
+ content: [{ type: "text", text: formatted + summary }],
225
+ };
226
+ } catch (err) {
227
+ const message = err instanceof Error ? err.message : String(err);
228
+ return {
229
+ content: [{ type: "text", text: `Failed to reach Refine Backlog API: ${message}` }],
230
+ isError: true,
231
+ };
232
+ }
233
+ });
234
+
235
+ async function main() {
236
+ const transport = new StdioServerTransport();
237
+ await server.connect(transport);
238
+ console.error("Refine Backlog MCP server running on stdio");
239
+ }
240
+
241
+ main().catch((err) => {
242
+ console.error("Fatal error:", err);
243
+ process.exit(1);
244
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
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
+ }