zopkit-bug-resolution-mcp 1.0.0 → 1.2.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 +92 -23
- package/index.js +232 -8
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,47 +1,116 @@
|
|
|
1
1
|
# bug-resolution MCP server
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
can
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
A four-tool MCP server so Claude Code (or Cursor, or any other MCP client)
|
|
4
|
+
can work a Zopkit bug report end to end as native tool calls, instead of
|
|
5
|
+
hand-rolling curl commands:
|
|
6
|
+
|
|
7
|
+
- **`fetch_bug_context`** — pulls the full report (title, description,
|
|
8
|
+
severity, status, page URL, browser info, discussion comments) plus every
|
|
9
|
+
inline screenshot as real image content, via
|
|
10
|
+
`GET /api/admin/bug-reports/context/:id`.
|
|
11
|
+
- **`submit_bug_resolution`** — writes back how it was fixed as a Markdown
|
|
12
|
+
writeup and moves the report to `resolved`, via
|
|
13
|
+
`POST /api/admin/bug-reports/resolution/:id`.
|
|
14
|
+
- **`fetch_assigned_bugs`** — lists reports currently assigned to the API
|
|
15
|
+
key's owner ("what's on my plate"), optionally filtered by status, via
|
|
16
|
+
`GET /api/admin/bug-reports/assigned`.
|
|
17
|
+
- **`create_bug_report`** — files a new bug report (plain-text description),
|
|
18
|
+
optionally self-assigning it, via `POST /api/admin/bug-reports/report`.
|
|
19
|
+
|
|
20
|
+
All four are thin wrappers — see `backend/src/features/bug-reports/README.md`
|
|
21
|
+
for what each endpoint actually does server-side.
|
|
22
|
+
|
|
23
|
+
Published to npm as
|
|
24
|
+
[`zopkit-bug-resolution-mcp`](https://www.npmjs.com/package/zopkit-bug-resolution-mcp)
|
|
25
|
+
— employees don't need this repo at all, just `npx` and a key.
|
|
9
26
|
|
|
10
27
|
## Setup
|
|
11
28
|
|
|
12
29
|
1. **Get a key.** In the company-admin panel → Bug Reports → **API Keys**
|
|
13
|
-
(
|
|
14
|
-
shown exactly once.
|
|
30
|
+
(any platform staff with `bug-reports` or `bug-reports-own` access can
|
|
31
|
+
generate their own), generate one and copy it — it's shown exactly once.
|
|
32
|
+
The key's reach (every report, or only reports you created/are assigned
|
|
33
|
+
to) always tracks your *current* staff role — it's resolved live on every
|
|
34
|
+
call, not fixed at generation time.
|
|
15
35
|
|
|
16
|
-
2. **Register the server
|
|
36
|
+
2. **Register the server** — either via the CLI:
|
|
17
37
|
|
|
18
38
|
```bash
|
|
19
|
-
cd deploy/mcp/bug-resolution
|
|
20
|
-
npm install
|
|
21
39
|
claude mcp add bug-resolution \
|
|
22
40
|
--env BUG_REPORT_API_KEY=<the key you copied> \
|
|
23
|
-
--
|
|
41
|
+
-- npx -y zopkit-bug-resolution-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
or by adding this to your MCP config JSON (Claude Desktop's
|
|
45
|
+
`claude_desktop_config.json`, or Claude Code's own MCP settings):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"bug-resolution": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["-y", "zopkit-bug-resolution-mcp"],
|
|
53
|
+
"env": {
|
|
54
|
+
"BUG_REPORT_API_KEY": "<the key you copied>"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
24
59
|
```
|
|
25
60
|
|
|
26
61
|
By default it talks to production (`https://api.zopkit.com`). Point it
|
|
27
|
-
elsewhere (e.g. a local dev backend) with an extra
|
|
28
|
-
|
|
62
|
+
elsewhere (e.g. a local dev backend) with an extra env var,
|
|
63
|
+
`BUG_REPORT_API_BASE_URL=http://localhost:3000`.
|
|
29
64
|
|
|
30
|
-
3. Restart Claude Code and approve `bug-resolution` when
|
|
65
|
+
3. Restart Claude Code (or Claude Desktop) and approve `bug-resolution` when
|
|
66
|
+
prompted.
|
|
67
|
+
|
|
68
|
+
<details>
|
|
69
|
+
<summary>Running from a local checkout instead (this repo, not npm)</summary>
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd deploy/mcp/bug-resolution
|
|
73
|
+
npm install
|
|
74
|
+
claude mcp add bug-resolution \
|
|
75
|
+
--env BUG_REPORT_API_KEY=<the key you copied> \
|
|
76
|
+
-- node "$(pwd)/index.js"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Useful if you're changing `index.js` itself — the npm package is what
|
|
80
|
+
everyone else should use.
|
|
81
|
+
</details>
|
|
31
82
|
|
|
32
83
|
## Usage
|
|
33
84
|
|
|
34
|
-
Once registered,
|
|
35
|
-
|
|
36
|
-
whoever asked you to fix it
|
|
37
|
-
|
|
38
|
-
|
|
85
|
+
Once registered, give Claude Code (or whichever tool) the bug report's id (a
|
|
86
|
+
UUID — copy it from the ID chip in the report's detail panel, or from
|
|
87
|
+
whoever asked you to fix it).
|
|
88
|
+
|
|
89
|
+
1. Ask it to call `fetch_bug_context` with that id first — you'll get the
|
|
90
|
+
title, description, severity/status, discussion, and every screenshot
|
|
91
|
+
actually rendered as images, so the tool can see the bug instead of just
|
|
92
|
+
reading a URL to it.
|
|
93
|
+
2. After fixing it, ask it to call `submit_bug_resolution` with the same id
|
|
94
|
+
and a Markdown writeup. It sets the report's resolution notes to that
|
|
95
|
+
writeup and moves its status straight to `resolved`.
|
|
96
|
+
|
|
97
|
+
Two more, independent of the fetch/resolve flow above:
|
|
98
|
+
|
|
99
|
+
- Ask it to call `fetch_assigned_bugs` (optionally with a `status` filter) to
|
|
100
|
+
see what's currently assigned to you — no id needed.
|
|
101
|
+
- Ask it to call `create_bug_report` with a `title` and plain-text
|
|
102
|
+
`description` to file something it noticed — pass `assignToSelf: true` to
|
|
103
|
+
claim it immediately.
|
|
39
104
|
|
|
40
105
|
## Notes
|
|
41
106
|
|
|
42
107
|
- Not part of the pnpm workspace (`pnpm-workspace.yaml`) or the deployed
|
|
43
|
-
backend image
|
|
108
|
+
backend image, and published separately from this repo's release process
|
|
109
|
+
(`npm publish` from this directory, manually, when `index.js` changes).
|
|
110
|
+
- No secrets in the package itself — the API key is supplied per-installation
|
|
111
|
+
via the `BUG_REPORT_API_KEY` env var, never embedded.
|
|
44
112
|
- The key is per-developer and individually revocable from the same API Keys
|
|
45
113
|
panel — revoking one never affects any other person's key.
|
|
46
|
-
-
|
|
47
|
-
|
|
114
|
+
- `fetch_bug_context` and `submit_bug_resolution` still need a report id —
|
|
115
|
+
this server doesn't search or browse. `fetch_assigned_bugs` is the one tool
|
|
116
|
+
that doesn't require knowing an id up front.
|
package/index.js
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// bug-resolution MCP server —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
2
|
+
// bug-resolution MCP server — four tools wrapping the bug-reports resolution
|
|
3
|
+
// API (see backend/src/features/bug-reports/README.md) so a developer's
|
|
4
|
+
// Claude Code / Cursor session can work a bug report end to end as native
|
|
5
|
+
// tool calls instead of hand-rolling curl commands:
|
|
6
|
+
// - fetch_bug_context: GET /api/admin/bug-reports/context/:id — pulls the
|
|
7
|
+
// full report (title/description/severity/status/comments) plus every
|
|
8
|
+
// inline screenshot as real image content.
|
|
9
|
+
// - submit_bug_resolution: POST /api/admin/bug-reports/resolution/:id —
|
|
10
|
+
// writes back the fix as a Markdown resolution and marks it resolved.
|
|
11
|
+
// - fetch_assigned_bugs: GET /api/admin/bug-reports/assigned — "what's on
|
|
12
|
+
// my plate", always the API key owner's own assigned reports.
|
|
13
|
+
// - create_bug_report: POST /api/admin/bug-reports/report — file a new bug
|
|
14
|
+
// the tool found, optionally self-assigning it.
|
|
15
|
+
// Thin wrapper: all the actual logic (auth, scoping, the DB writes) lives
|
|
16
|
+
// server-side; this just forwards the HTTP calls the README documents.
|
|
9
17
|
//
|
|
10
18
|
// Setup: see README.md in this directory.
|
|
11
19
|
|
|
@@ -21,7 +29,30 @@ if (!API_KEY) {
|
|
|
21
29
|
'Generate one from the company-admin panel (Bug Reports → API Keys) and set it when registering this server.');
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.
|
|
32
|
+
const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.2.0' });
|
|
33
|
+
|
|
34
|
+
const BUG_SEVERITIES = ['low', 'medium', 'high', 'critical'];
|
|
35
|
+
const BUG_STATUSES = ['open', 'in_progress', 'blocked', 'further_discussion', 'resolved'];
|
|
36
|
+
const BUG_PROJECTS = ['wrapper', 'crm', 'fa', 'accounting', 'academy', 'itsm', 'studio'];
|
|
37
|
+
|
|
38
|
+
const IMG_SRC = /<img\b[^>]*\bsrc="([^"]+)"[^>]*>/gi;
|
|
39
|
+
|
|
40
|
+
function extractImageUrls(html) {
|
|
41
|
+
const urls = [];
|
|
42
|
+
for (const match of html.matchAll(IMG_SRC)) {
|
|
43
|
+
const src = match[1];
|
|
44
|
+
urls.push(src.startsWith('http') ? src : `${BASE_URL}${src}`);
|
|
45
|
+
}
|
|
46
|
+
return urls;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function fetchImageContentBlock(url) {
|
|
50
|
+
const res = await fetch(url);
|
|
51
|
+
if (!res.ok) return null;
|
|
52
|
+
const contentType = res.headers.get('content-type') || 'image/png';
|
|
53
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
54
|
+
return { type: 'image', data: buffer.toString('base64'), mimeType: contentType };
|
|
55
|
+
}
|
|
25
56
|
|
|
26
57
|
server.registerTool(
|
|
27
58
|
'submit_bug_resolution',
|
|
@@ -83,5 +114,198 @@ server.registerTool(
|
|
|
83
114
|
}
|
|
84
115
|
);
|
|
85
116
|
|
|
117
|
+
server.registerTool(
|
|
118
|
+
'fetch_bug_context',
|
|
119
|
+
{
|
|
120
|
+
title: 'Fetch bug report context',
|
|
121
|
+
description:
|
|
122
|
+
'Pull the full context for a Zopkit bug report — title, description, severity, status, ' +
|
|
123
|
+
'page URL, browser info, discussion comments, and every inline screenshot as actual images — ' +
|
|
124
|
+
'so an AI coding tool has everything it needs to diagnose and fix the bug without ' +
|
|
125
|
+
'opening the admin panel. Screenshots come back as image content alongside the text summary.',
|
|
126
|
+
inputSchema: {
|
|
127
|
+
bugReportId: z.string().min(1).describe("The bug report's id (UUID)."),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
async ({ bugReportId }) => {
|
|
131
|
+
if (!API_KEY) {
|
|
132
|
+
return {
|
|
133
|
+
isError: true,
|
|
134
|
+
content: [{ type: 'text', text: 'BUG_REPORT_API_KEY is not set on this MCP server — see README.md.' }],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let res;
|
|
139
|
+
try {
|
|
140
|
+
res = await fetch(`${BASE_URL}/api/admin/bug-reports/context/${encodeURIComponent(bugReportId)}`, {
|
|
141
|
+
headers: { 'X-API-Key': API_KEY },
|
|
142
|
+
});
|
|
143
|
+
} catch (err) {
|
|
144
|
+
return {
|
|
145
|
+
isError: true,
|
|
146
|
+
content: [{ type: 'text', text: `Could not reach ${BASE_URL}: ${err instanceof Error ? err.message : String(err)}` }],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const body = await res.json().catch(() => null);
|
|
151
|
+
|
|
152
|
+
if (!res.ok) {
|
|
153
|
+
const message = body?.message || body?.error || `HTTP ${res.status}`;
|
|
154
|
+
return { isError: true, content: [{ type: 'text', text: `Failed to fetch bug context: ${message}` }] };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const report = body?.data;
|
|
158
|
+
if (!report) {
|
|
159
|
+
return { isError: true, content: [{ type: 'text', text: 'Bug context response had no data.' }] };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const comments = report.comments || [];
|
|
163
|
+
const commentsText = comments.length
|
|
164
|
+
? comments.map((c) => `- ${c.authorName || c.authorEmail} (${c.createdAt}): ${c.body}`).join('\n')
|
|
165
|
+
: '(no comments)';
|
|
166
|
+
|
|
167
|
+
const summary = [
|
|
168
|
+
`# ${report.title}`,
|
|
169
|
+
`id: ${report.id}`,
|
|
170
|
+
`severity: ${report.severity} | status: ${report.status}`,
|
|
171
|
+
report.pageUrl ? `page: ${report.pageUrl}` : null,
|
|
172
|
+
report.browserInfo ? `browser: ${report.browserInfo}` : null,
|
|
173
|
+
'',
|
|
174
|
+
'## Description',
|
|
175
|
+
report.description,
|
|
176
|
+
'',
|
|
177
|
+
'## Discussion',
|
|
178
|
+
commentsText,
|
|
179
|
+
].filter((line) => line !== null).join('\n');
|
|
180
|
+
|
|
181
|
+
const content = [{ type: 'text', text: summary }];
|
|
182
|
+
|
|
183
|
+
// Inline screenshots live as <img> tags inside the description HTML —
|
|
184
|
+
// fetch each (now-public) media URL and hand it back as real image
|
|
185
|
+
// content so the AI tool actually sees the bug, not just a URL to it.
|
|
186
|
+
const imageUrls = extractImageUrls(report.description || '');
|
|
187
|
+
for (const url of imageUrls) {
|
|
188
|
+
try {
|
|
189
|
+
const block = await fetchImageContentBlock(url);
|
|
190
|
+
if (block) content.push(block);
|
|
191
|
+
} catch {
|
|
192
|
+
content.push({ type: 'text', text: `(failed to load screenshot: ${url})` });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { content };
|
|
197
|
+
}
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
server.registerTool(
|
|
201
|
+
'fetch_assigned_bugs',
|
|
202
|
+
{
|
|
203
|
+
title: 'Fetch bugs assigned to me',
|
|
204
|
+
description:
|
|
205
|
+
"List Zopkit bug reports currently assigned to this API key's owner — \"what's on my plate\". " +
|
|
206
|
+
'Always the key owner\'s own assigned reports, never anyone else\'s, regardless of what the key ' +
|
|
207
|
+
'can otherwise see. Optionally filter by status.',
|
|
208
|
+
inputSchema: {
|
|
209
|
+
status: z.enum(BUG_STATUSES).optional().describe('Only return reports in this status.'),
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
async ({ status }) => {
|
|
213
|
+
if (!API_KEY) {
|
|
214
|
+
return {
|
|
215
|
+
isError: true,
|
|
216
|
+
content: [{ type: 'text', text: 'BUG_REPORT_API_KEY is not set on this MCP server — see README.md.' }],
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const url = new URL(`${BASE_URL}/api/admin/bug-reports/assigned`);
|
|
221
|
+
if (status) url.searchParams.set('status', status);
|
|
222
|
+
|
|
223
|
+
let res;
|
|
224
|
+
try {
|
|
225
|
+
res = await fetch(url, { headers: { 'X-API-Key': API_KEY } });
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return {
|
|
228
|
+
isError: true,
|
|
229
|
+
content: [{ type: 'text', text: `Could not reach ${BASE_URL}: ${err instanceof Error ? err.message : String(err)}` }],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const body = await res.json().catch(() => null);
|
|
234
|
+
|
|
235
|
+
if (!res.ok) {
|
|
236
|
+
const message = body?.message || body?.error || `HTTP ${res.status}`;
|
|
237
|
+
return { isError: true, content: [{ type: 'text', text: `Failed to fetch assigned bugs: ${message}` }] };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const reports = body?.data || [];
|
|
241
|
+
if (reports.length === 0) {
|
|
242
|
+
return { content: [{ type: 'text', text: 'No bug reports currently assigned to you.' }] };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const summary = reports
|
|
246
|
+
.map((r) => `- [${r.severity}/${r.status}] ${r.title} (id: ${r.id})${r.project ? ` — project: ${r.project}` : ''}`)
|
|
247
|
+
.join('\n');
|
|
248
|
+
|
|
249
|
+
return { content: [{ type: 'text', text: `${reports.length} assigned report(s):\n${summary}` }] };
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
server.registerTool(
|
|
254
|
+
'create_bug_report',
|
|
255
|
+
{
|
|
256
|
+
title: 'Create a bug report',
|
|
257
|
+
description:
|
|
258
|
+
'File a new Zopkit bug report — e.g. something this tool noticed while working on something else. ' +
|
|
259
|
+
'Description is plain text (paragraphs separated by a blank line), not rich-text HTML.',
|
|
260
|
+
inputSchema: {
|
|
261
|
+
title: z.string().min(1).max(255).describe('A short, specific summary of the bug.'),
|
|
262
|
+
description: z.string().min(1).describe('The bug, in plain text — what happened, expected vs actual, repro steps.'),
|
|
263
|
+
severity: z.enum(BUG_SEVERITIES).optional().describe('Defaults to "medium".'),
|
|
264
|
+
project: z.enum(BUG_PROJECTS).optional().describe('Which product this bug is in, if known.'),
|
|
265
|
+
pageUrl: z.string().max(2048).optional().describe('The URL where the bug was observed, if applicable.'),
|
|
266
|
+
assignToSelf: z.boolean().optional().describe('Self-assign this report to the API key owner. Defaults to false (unassigned).'),
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
async ({ title, description, severity, project, pageUrl, assignToSelf }) => {
|
|
270
|
+
if (!API_KEY) {
|
|
271
|
+
return {
|
|
272
|
+
isError: true,
|
|
273
|
+
content: [{ type: 'text', text: 'BUG_REPORT_API_KEY is not set on this MCP server — see README.md.' }],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let res;
|
|
278
|
+
try {
|
|
279
|
+
res = await fetch(`${BASE_URL}/api/admin/bug-reports/report`, {
|
|
280
|
+
method: 'POST',
|
|
281
|
+
headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
|
|
282
|
+
body: JSON.stringify({ title, description, severity, project, pageUrl, assignToSelf }),
|
|
283
|
+
});
|
|
284
|
+
} catch (err) {
|
|
285
|
+
return {
|
|
286
|
+
isError: true,
|
|
287
|
+
content: [{ type: 'text', text: `Could not reach ${BASE_URL}: ${err instanceof Error ? err.message : String(err)}` }],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const body = await res.json().catch(() => null);
|
|
292
|
+
|
|
293
|
+
if (!res.ok) {
|
|
294
|
+
const message = body?.message || body?.error || `HTTP ${res.status}`;
|
|
295
|
+
return { isError: true, content: [{ type: 'text', text: `Failed to create bug report: ${message}` }] };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const report = body?.data;
|
|
299
|
+
return {
|
|
300
|
+
content: [{
|
|
301
|
+
type: 'text',
|
|
302
|
+
text: report
|
|
303
|
+
? `Bug report created: "${report.title}" (id: ${report.id}).`
|
|
304
|
+
: 'Bug report created.',
|
|
305
|
+
}],
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
|
|
86
310
|
const transport = new StdioServerTransport();
|
|
87
311
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zopkit-bug-resolution-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "MCP server: submit how a bug
|
|
5
|
+
"description": "MCP server for Zopkit wrapper bug reports: fetch full bug context (with screenshots), submit how a bug was fixed, list bugs assigned to you, and file new bug reports. Requires a BUG_REPORT_API_KEY (generated in the company-admin panel) — see README.md.",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=18"
|