zopkit-bug-resolution-mcp 1.2.0 → 1.5.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 +51 -15
- package/index.js +109 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
# bug-resolution MCP server
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
can work a Zopkit bug report end to end as native tool calls
|
|
5
|
-
hand-rolling curl commands:
|
|
3
|
+
A five-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 — plus report
|
|
5
|
+
usage status — instead of hand-rolling curl commands:
|
|
6
6
|
|
|
7
7
|
- **`fetch_bug_context`** — pulls the full report (title, description,
|
|
8
8
|
severity, status, page URL, browser info, discussion comments) plus every
|
|
9
9
|
inline screenshot as real image content, via
|
|
10
|
-
`GET /api/admin/bug-reports/context/:id`.
|
|
10
|
+
`GET /api/admin/bug-reports/context/:id`. Calling it IS starting work on
|
|
11
|
+
the ticket: `open`/`failed_testing` auto-moves to `in_progress` as a side
|
|
12
|
+
effect (anything further along is left untouched).
|
|
11
13
|
- **`submit_bug_resolution`** — writes back how it was fixed as a Markdown
|
|
12
|
-
writeup and moves the report to `resolved
|
|
14
|
+
writeup and moves the report to `needs_testing` (not `resolved` — a human
|
|
15
|
+
still verifies the fix first), via
|
|
13
16
|
`POST /api/admin/bug-reports/resolution/:id`.
|
|
14
17
|
- **`fetch_assigned_bugs`** — lists reports currently assigned to the API
|
|
15
|
-
key's owner ("what's on my plate"),
|
|
18
|
+
key's owner ("what's on my plate"), defaulting to everything but
|
|
19
|
+
`resolved` and sorted by what needs attention soonest, optionally filtered
|
|
20
|
+
to specific statuses and paginated, via
|
|
16
21
|
`GET /api/admin/bug-reports/assigned`.
|
|
17
22
|
- **`create_bug_report`** — files a new bug report (plain-text description),
|
|
18
23
|
optionally self-assigning it, via `POST /api/admin/bug-reports/report`.
|
|
24
|
+
- **`report_claude_usage`** — self-reports this developer's current Claude
|
|
25
|
+
Code usage percentage (there's no API to fetch it on your behalf — see
|
|
26
|
+
`backend/src/features/claude-usage/README.md`), shown on the
|
|
27
|
+
company-admin panel's "Claude Usage" tab, via `POST /api/admin/claude-usage`.
|
|
19
28
|
|
|
20
|
-
All
|
|
21
|
-
for what each endpoint
|
|
29
|
+
All five are thin wrappers — see `backend/src/features/bug-reports/README.md`
|
|
30
|
+
and `backend/src/features/claude-usage/README.md` for what each endpoint
|
|
31
|
+
actually does server-side.
|
|
22
32
|
|
|
23
33
|
Published to npm as
|
|
24
34
|
[`zopkit-bug-resolution-mcp`](https://www.npmjs.com/package/zopkit-bug-resolution-mcp)
|
|
@@ -62,6 +72,19 @@ Published to npm as
|
|
|
62
72
|
elsewhere (e.g. a local dev backend) with an extra env var,
|
|
63
73
|
`BUG_REPORT_API_BASE_URL=http://localhost:3000`.
|
|
64
74
|
|
|
75
|
+
**On Windows**, a bare `npx` command as shown above commonly fails or
|
|
76
|
+
hangs (MCP client "connection timed out after 30000ms") — Windows needs
|
|
77
|
+
`npx.cmd` resolved through `cmd.exe`, not the bare `npx` name spawn()
|
|
78
|
+
looks for on macOS/Linux. Route it through `cmd /c` instead:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
claude mcp add bug-resolution \
|
|
82
|
+
--env BUG_REPORT_API_KEY=<the key you copied> \
|
|
83
|
+
-- cmd /c npx -y zopkit-bug-resolution-mcp
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
or in the JSON config, `"command": "cmd", "args": ["/c", "npx", "-y", "zopkit-bug-resolution-mcp"]`.
|
|
87
|
+
|
|
65
88
|
3. Restart Claude Code (or Claude Desktop) and approve `bug-resolution` when
|
|
66
89
|
prompted.
|
|
67
90
|
|
|
@@ -89,18 +112,31 @@ whoever asked you to fix it).
|
|
|
89
112
|
1. Ask it to call `fetch_bug_context` with that id first — you'll get the
|
|
90
113
|
title, description, severity/status, discussion, and every screenshot
|
|
91
114
|
actually rendered as images, so the tool can see the bug instead of just
|
|
92
|
-
reading a URL to it.
|
|
115
|
+
reading a URL to it. This call also moves the board card to `In
|
|
116
|
+
Progress` if it was sitting at `Open`/`Failed Testing` — the board
|
|
117
|
+
reflects that work has started without anyone dragging the card by hand.
|
|
93
118
|
2. After fixing it, ask it to call `submit_bug_resolution` with the same id
|
|
94
119
|
and a Markdown writeup. It sets the report's resolution notes to that
|
|
95
|
-
writeup and moves its status
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
writeup and moves its status to `needs_testing` — a human still needs to
|
|
121
|
+
verify it before it's `resolved`.
|
|
122
|
+
|
|
123
|
+
Three more, independent of the fetch/resolve flow above:
|
|
124
|
+
|
|
125
|
+
- Ask it to call `fetch_assigned_bugs` to see what's currently assigned to
|
|
126
|
+
you — no id needed. By default it shows everything except `resolved`,
|
|
127
|
+
ranked by what needs your attention soonest (a fix QA bounced back first,
|
|
128
|
+
then active work, then fixes just waiting on QA). Pass
|
|
129
|
+
`status: ['failed_testing']` etc. to narrow it down, or `status: ['all']`
|
|
130
|
+
to include resolved history; `limit`/`offset` page past the first batch
|
|
131
|
+
if it says there's more.
|
|
101
132
|
- Ask it to call `create_bug_report` with a `title` and plain-text
|
|
102
133
|
`description` to file something it noticed — pass `assignToSelf: true` to
|
|
103
134
|
claim it immediately.
|
|
135
|
+
- Ask it to call `report_claude_usage` with your current usage `%` (from
|
|
136
|
+
`/usage`, the status line, or the account's billing page — there's no way
|
|
137
|
+
for the tool to look this up itself) to update the "Claude Usage" tab in
|
|
138
|
+
the company-admin panel. Pass an optional `note` for context (e.g. "Team
|
|
139
|
+
plan, resets Monday").
|
|
104
140
|
|
|
105
141
|
## Notes
|
|
106
142
|
|
package/index.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// bug-resolution MCP server —
|
|
2
|
+
// bug-resolution MCP server — five tools wrapping the bug-reports resolution
|
|
3
3
|
// API (see backend/src/features/bug-reports/README.md) so a developer's
|
|
4
4
|
// Claude Code / Cursor session can work a bug report end to end as native
|
|
5
5
|
// tool calls instead of hand-rolling curl commands:
|
|
6
6
|
// - fetch_bug_context: GET /api/admin/bug-reports/context/:id — pulls the
|
|
7
7
|
// full report (title/description/severity/status/comments) plus every
|
|
8
|
-
// inline screenshot as real image content.
|
|
8
|
+
// inline screenshot as real image content. Also IS "starting work on
|
|
9
|
+
// it": open/failed_testing auto-moves to in_progress as a side effect.
|
|
9
10
|
// - submit_bug_resolution: POST /api/admin/bug-reports/resolution/:id —
|
|
10
|
-
// writes back the fix as a Markdown resolution and
|
|
11
|
+
// writes back the fix as a Markdown resolution and moves it to
|
|
12
|
+
// needs_testing (not resolved — a human verifies it first).
|
|
11
13
|
// - fetch_assigned_bugs: GET /api/admin/bug-reports/assigned — "what's on
|
|
12
14
|
// my plate", always the API key owner's own assigned reports.
|
|
13
15
|
// - create_bug_report: POST /api/admin/bug-reports/report — file a new bug
|
|
14
16
|
// the tool found, optionally self-assigning it.
|
|
17
|
+
// - report_claude_usage: POST /api/admin/claude-usage — self-reports this
|
|
18
|
+
// developer's current Claude Code usage percentage (see
|
|
19
|
+
// backend/src/features/claude-usage/README.md for why this is
|
|
20
|
+
// self-reported rather than fetched from an Anthropic API), shown on
|
|
21
|
+
// the company-admin panel's "Claude Usage" tab.
|
|
15
22
|
// Thin wrapper: all the actual logic (auth, scoping, the DB writes) lives
|
|
16
23
|
// server-side; this just forwards the HTTP calls the README documents.
|
|
17
24
|
//
|
|
@@ -29,11 +36,16 @@ if (!API_KEY) {
|
|
|
29
36
|
'Generate one from the company-admin panel (Bug Reports → API Keys) and set it when registering this server.');
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.
|
|
39
|
+
const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.5.0' });
|
|
33
40
|
|
|
34
41
|
const BUG_SEVERITIES = ['low', 'medium', 'high', 'critical'];
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
// Keep in sync with BUG_STATUSES in backend/src/db/schema/platform/bug-reports.ts.
|
|
43
|
+
// This list was missing needs_testing/failed_testing for a while, which meant
|
|
44
|
+
// fetch_assigned_bugs couldn't even be asked to filter to exactly the two
|
|
45
|
+
// statuses a developer cares about most (a fresh fix bounced back by QA, or
|
|
46
|
+
// one still waiting on QA) — the backend has allowed both for a while.
|
|
47
|
+
const BUG_STATUSES = ['open', 'in_progress', 'needs_testing', 'failed_testing', 'blocked', 'further_discussion', 'resolved'];
|
|
48
|
+
const BUG_PROJECTS = ['wrapper', 'crm', 'fa', 'accounting', 'academy', 'itsm', 'studio', 'retail_erp', 'entertainment_erp'];
|
|
37
49
|
|
|
38
50
|
const IMG_SRC = /<img\b[^>]*\bsrc="([^"]+)"[^>]*>/gi;
|
|
39
51
|
|
|
@@ -60,7 +72,8 @@ server.registerTool(
|
|
|
60
72
|
title: 'Submit bug resolution',
|
|
61
73
|
description:
|
|
62
74
|
"Report how a Zopkit bug report was fixed. Sets the report's resolution notes to the given " +
|
|
63
|
-
"Markdown writeup and moves its status
|
|
75
|
+
"Markdown writeup and moves its status to 'needs_testing' — NOT 'resolved' directly; a human " +
|
|
76
|
+
"still needs to verify the fix before it's truly done. Use the bug report's id " +
|
|
64
77
|
'(a UUID, given to you by whoever asked you to fix it, or visible in its admin-panel URL).',
|
|
65
78
|
inputSchema: {
|
|
66
79
|
bugReportId: z.string().min(1).describe("The bug report's id (UUID)."),
|
|
@@ -122,7 +135,10 @@ server.registerTool(
|
|
|
122
135
|
'Pull the full context for a Zopkit bug report — title, description, severity, status, ' +
|
|
123
136
|
'page URL, browser info, discussion comments, and every inline screenshot as actual images — ' +
|
|
124
137
|
'so an AI coding tool has everything it needs to diagnose and fix the bug without ' +
|
|
125
|
-
|
|
138
|
+
"opening the admin panel. Screenshots come back as image content alongside the text summary. " +
|
|
139
|
+
"Calling this IS starting work on it: a report sitting at 'open' or 'failed_testing' moves " +
|
|
140
|
+
"straight to 'in_progress' as a side effect (anything further along — in_progress itself, " +
|
|
141
|
+
"needs_testing, blocked, further_discussion, resolved — is left untouched).",
|
|
126
142
|
inputSchema: {
|
|
127
143
|
bugReportId: z.string().min(1).describe("The bug report's id (UUID)."),
|
|
128
144
|
},
|
|
@@ -203,13 +219,24 @@ server.registerTool(
|
|
|
203
219
|
title: 'Fetch bugs assigned to me',
|
|
204
220
|
description:
|
|
205
221
|
"List Zopkit bug reports currently assigned to this API key's owner — \"what's on my plate\". " +
|
|
206
|
-
|
|
207
|
-
|
|
222
|
+
"Always the key owner's own assigned reports, never anyone else's, regardless of what the key " +
|
|
223
|
+
"can otherwise see. Defaults to every status EXCEPT 'resolved' (pass status: ['all'] to include " +
|
|
224
|
+
"resolved history too), sorted with whatever needs attention soonest first (a fix QA bounced back " +
|
|
225
|
+
"via 'failed_testing', then active work, then fixes merely awaiting QA). Paginated — use limit/offset " +
|
|
226
|
+
'if the result says there are more than fit on one page.',
|
|
208
227
|
inputSchema: {
|
|
209
|
-
status: z
|
|
228
|
+
status: z
|
|
229
|
+
.array(z.enum([...BUG_STATUSES, 'all']))
|
|
230
|
+
.optional()
|
|
231
|
+
.describe(
|
|
232
|
+
"One or more statuses to filter to. Omit for the default (everything but 'resolved'); " +
|
|
233
|
+
"pass ['all'] to include resolved history too."
|
|
234
|
+
),
|
|
235
|
+
limit: z.number().int().min(1).max(100).optional().describe('Max reports to return (default 50, max 100).'),
|
|
236
|
+
offset: z.number().int().min(0).optional().describe('Skip this many reports, for paging past the first batch.'),
|
|
210
237
|
},
|
|
211
238
|
},
|
|
212
|
-
async ({ status }) => {
|
|
239
|
+
async ({ status, limit, offset }) => {
|
|
213
240
|
if (!API_KEY) {
|
|
214
241
|
return {
|
|
215
242
|
isError: true,
|
|
@@ -218,7 +245,9 @@ server.registerTool(
|
|
|
218
245
|
}
|
|
219
246
|
|
|
220
247
|
const url = new URL(`${BASE_URL}/api/admin/bug-reports/assigned`);
|
|
221
|
-
if (status) url.searchParams.set('status', status);
|
|
248
|
+
if (status?.length) url.searchParams.set('status', status.join(','));
|
|
249
|
+
if (limit != null) url.searchParams.set('limit', String(limit));
|
|
250
|
+
if (offset != null) url.searchParams.set('offset', String(offset));
|
|
222
251
|
|
|
223
252
|
let res;
|
|
224
253
|
try {
|
|
@@ -238,15 +267,22 @@ server.registerTool(
|
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
const reports = body?.data || [];
|
|
270
|
+
const meta = body?.meta;
|
|
241
271
|
if (reports.length === 0) {
|
|
242
|
-
return { content: [{ type: 'text', text: 'No bug reports currently assigned to you.' }] };
|
|
272
|
+
return { content: [{ type: 'text', text: 'No bug reports currently assigned to you (in the requested status(es)).' }] };
|
|
243
273
|
}
|
|
244
274
|
|
|
245
275
|
const summary = reports
|
|
246
276
|
.map((r) => `- [${r.severity}/${r.status}] ${r.title} (id: ${r.id})${r.project ? ` — project: ${r.project}` : ''}`)
|
|
247
277
|
.join('\n');
|
|
248
278
|
|
|
249
|
-
|
|
279
|
+
// meta.total counts everything matching the filter, not just this page —
|
|
280
|
+
// say so explicitly instead of letting a truncated page read as "that's all of it".
|
|
281
|
+
const header = meta && meta.total > reports.length
|
|
282
|
+
? `${reports.length} of ${meta.total} assigned report(s) (showing offset ${meta.offset}–${meta.offset + reports.length - 1}; pass a higher offset for more):`
|
|
283
|
+
: `${reports.length} assigned report(s):`;
|
|
284
|
+
|
|
285
|
+
return { content: [{ type: 'text', text: `${header}\n${summary}` }] };
|
|
250
286
|
}
|
|
251
287
|
);
|
|
252
288
|
|
|
@@ -307,5 +343,63 @@ server.registerTool(
|
|
|
307
343
|
}
|
|
308
344
|
);
|
|
309
345
|
|
|
346
|
+
server.registerTool(
|
|
347
|
+
'report_claude_usage',
|
|
348
|
+
{
|
|
349
|
+
title: 'Report Claude usage status',
|
|
350
|
+
description:
|
|
351
|
+
"Report this developer's current Claude Code account/session usage percentage, shown on the " +
|
|
352
|
+
'company-admin panel\'s "Claude Usage" tab. Self-reported — there is no API to fetch this on your ' +
|
|
353
|
+
'behalf (Anthropic has no per-seat "current usage %" endpoint, and the percentage Claude Code\'s own ' +
|
|
354
|
+
'/usage shows is computed client-side from this session\'s own rate-limit headers, not retrievable by ' +
|
|
355
|
+
'a third party). Pass whatever percentage you or the user currently see — e.g. from the /usage ' +
|
|
356
|
+
"command, the status line, or the account's billing page. Identity (who this is) is resolved from " +
|
|
357
|
+
'your API key, not from anything passed here.',
|
|
358
|
+
inputSchema: {
|
|
359
|
+
usagePercent: z.number().min(0).max(100).describe('Current usage, 0-100 (fractional values like 72.5 are fine).'),
|
|
360
|
+
note: z.string().max(500).optional().describe('Optional context, e.g. "Team plan, resets Monday".'),
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
async ({ usagePercent, note }) => {
|
|
364
|
+
if (!API_KEY) {
|
|
365
|
+
return {
|
|
366
|
+
isError: true,
|
|
367
|
+
content: [{ type: 'text', text: 'BUG_REPORT_API_KEY is not set on this MCP server — see README.md.' }],
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let res;
|
|
372
|
+
try {
|
|
373
|
+
res = await fetch(`${BASE_URL}/api/admin/claude-usage`, {
|
|
374
|
+
method: 'POST',
|
|
375
|
+
headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
|
|
376
|
+
body: JSON.stringify({ usagePercent, note }),
|
|
377
|
+
});
|
|
378
|
+
} catch (err) {
|
|
379
|
+
return {
|
|
380
|
+
isError: true,
|
|
381
|
+
content: [{ type: 'text', text: `Could not reach ${BASE_URL}: ${err instanceof Error ? err.message : String(err)}` }],
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const body = await res.json().catch(() => null);
|
|
386
|
+
|
|
387
|
+
if (!res.ok) {
|
|
388
|
+
const message = body?.message || body?.error || `HTTP ${res.status}`;
|
|
389
|
+
return { isError: true, content: [{ type: 'text', text: `Failed to report Claude usage: ${message}` }] };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const status = body?.data;
|
|
393
|
+
return {
|
|
394
|
+
content: [{
|
|
395
|
+
type: 'text',
|
|
396
|
+
text: status
|
|
397
|
+
? `Reported ${status.usagePercent}% usage for ${status.email}.`
|
|
398
|
+
: 'Usage reported.',
|
|
399
|
+
}],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
|
|
310
404
|
const transport = new StdioServerTransport();
|
|
311
405
|
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.5.0",
|
|
4
4
|
"type": "module",
|
|
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,
|
|
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, file new bug reports, and report your Claude Code usage status. 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"
|