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 +107 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.js +181 -0
- package/package.json +23 -0
- package/server.ts +244 -0
- package/tsconfig.json +15 -0
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)
|
package/dist/server.d.ts
ADDED
|
@@ -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
|
+
}
|