zopkit-bug-resolution-mcp 1.2.1 → 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.
Files changed (3) hide show
  1. package/README.md +38 -15
  2. package/index.js +109 -15
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,24 +1,34 @@
1
1
  # bug-resolution MCP server
2
2
 
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:
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`, via
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"), optionally filtered by status, via
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 four are thin wrappers — see `backend/src/features/bug-reports/README.md`
21
- for what each endpoint actually does server-side.
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)
@@ -102,18 +112,31 @@ whoever asked you to fix it).
102
112
  1. Ask it to call `fetch_bug_context` with that id first — you'll get the
103
113
  title, description, severity/status, discussion, and every screenshot
104
114
  actually rendered as images, so the tool can see the bug instead of just
105
- 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.
106
118
  2. After fixing it, ask it to call `submit_bug_resolution` with the same id
107
119
  and a Markdown writeup. It sets the report's resolution notes to that
108
- writeup and moves its status straight to `resolved`.
109
-
110
- Two more, independent of the fetch/resolve flow above:
111
-
112
- - Ask it to call `fetch_assigned_bugs` (optionally with a `status` filter) to
113
- see what's currently assigned to you — no id needed.
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.
114
132
  - Ask it to call `create_bug_report` with a `title` and plain-text
115
133
  `description` to file something it noticed — pass `assignToSelf: true` to
116
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").
117
140
 
118
141
  ## Notes
119
142
 
package/index.js CHANGED
@@ -1,17 +1,24 @@
1
1
  #!/usr/bin/env node
2
- // bug-resolution MCP server — four tools wrapping the bug-reports resolution
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 marks it resolved.
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.2.0' });
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
- const BUG_STATUSES = ['open', 'in_progress', 'blocked', 'further_discussion', 'resolved'];
36
- const BUG_PROJECTS = ['wrapper', 'crm', 'fa', 'accounting', 'academy', 'itsm', 'studio'];
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 straight to 'resolved'. Use the bug report's id " +
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
- 'opening the admin panel. Screenshots come back as image content alongside the text summary.',
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
- '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.',
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.enum(BUG_STATUSES).optional().describe('Only return reports in this status.'),
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
- return { content: [{ type: 'text', text: `${reports.length} assigned report(s):\n${summary}` }] };
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.2.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, and file new bug reports. Requires a BUG_REPORT_API_KEY (generated in the company-admin panel) — see README.md.",
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"