zopkit-bug-resolution-mcp 1.5.0 → 1.6.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 +27 -4
- package/index.js +282 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# bug-resolution MCP server
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A six-tool MCP server so Claude Code (or Cursor, or any other MCP client)
|
|
4
4
|
can work a Zopkit bug report end to end as native tool calls — plus report
|
|
5
5
|
usage status — instead of hand-rolling curl commands:
|
|
6
6
|
|
|
@@ -25,8 +25,22 @@ usage status — instead of hand-rolling curl commands:
|
|
|
25
25
|
Code usage percentage (there's no API to fetch it on your behalf — see
|
|
26
26
|
`backend/src/features/claude-usage/README.md`), shown on the
|
|
27
27
|
company-admin panel's "Claude Usage" tab, via `POST /api/admin/claude-usage`.
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
- **`report_claude_token_usage`** — unlike the one above, this one is NOT
|
|
29
|
+
self-attested: it scans this developer's own local Claude Code transcripts
|
|
30
|
+
(`~/.claude/projects/**/*.jsonl`, same machine the MCP server runs on — no
|
|
31
|
+
`/usage`, no status line, no asking a human) for the last 7 days, dedupes
|
|
32
|
+
streamed duplicates by `message.id`, and reports real input/output/
|
|
33
|
+
cache-creation/cache-read token totals, session count, and whether any
|
|
34
|
+
sessions used web search or spawned subagents, via
|
|
35
|
+
`POST /api/admin/claude-usage/token-breakdown`. Also runs automatically,
|
|
36
|
+
fire-and-forget, as a side effect of the four bug-report tools above
|
|
37
|
+
(`submit_bug_resolution`, `fetch_bug_context`, `fetch_assigned_bugs`,
|
|
38
|
+
`create_bug_report`) on success — so a developer who never touches this
|
|
39
|
+
tool directly still gets a token breakdown reported just by doing normal
|
|
40
|
+
bug-report work. Failures from the auto-report path are swallowed
|
|
41
|
+
entirely; they never surface as a tool error.
|
|
42
|
+
|
|
43
|
+
All six are thin wrappers — see `backend/src/features/bug-reports/README.md`
|
|
30
44
|
and `backend/src/features/claude-usage/README.md` for what each endpoint
|
|
31
45
|
actually does server-side.
|
|
32
46
|
|
|
@@ -120,7 +134,7 @@ whoever asked you to fix it).
|
|
|
120
134
|
writeup and moves its status to `needs_testing` — a human still needs to
|
|
121
135
|
verify it before it's `resolved`.
|
|
122
136
|
|
|
123
|
-
|
|
137
|
+
Four more, independent of the fetch/resolve flow above:
|
|
124
138
|
|
|
125
139
|
- Ask it to call `fetch_assigned_bugs` to see what's currently assigned to
|
|
126
140
|
you — no id needed. By default it shows everything except `resolved`,
|
|
@@ -137,6 +151,15 @@ Three more, independent of the fetch/resolve flow above:
|
|
|
137
151
|
for the tool to look this up itself) to update the "Claude Usage" tab in
|
|
138
152
|
the company-admin panel. Pass an optional `note` for context (e.g. "Team
|
|
139
153
|
plan, resets Monday").
|
|
154
|
+
- Ask it to call `report_claude_token_usage` (no input needed) to push a
|
|
155
|
+
real token/session breakdown for the last 7 days up to the same "Claude
|
|
156
|
+
Usage" tab — distinct from and independent of `report_claude_usage`
|
|
157
|
+
above. You usually don't need to call this directly: it already fires
|
|
158
|
+
automatically after every successful `fetch_bug_context`,
|
|
159
|
+
`submit_bug_resolution`, `fetch_assigned_bugs`, or `create_bug_report`
|
|
160
|
+
call, and a short note gets appended to that tool's own response telling
|
|
161
|
+
you it happened (e.g. "Also updated your Claude token usage: 2.1M new
|
|
162
|
+
tokens across 3 session(s) this week.").
|
|
140
163
|
|
|
141
164
|
## Notes
|
|
142
165
|
|
package/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// bug-resolution MCP server —
|
|
2
|
+
// bug-resolution MCP server — six 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:
|
|
@@ -14,11 +14,18 @@
|
|
|
14
14
|
// my plate", always the API key owner's own assigned reports.
|
|
15
15
|
// - create_bug_report: POST /api/admin/bug-reports/report — file a new bug
|
|
16
16
|
// the tool found, optionally self-assigning it.
|
|
17
|
-
// - report_claude_usage: POST /api/admin/claude-usage —
|
|
18
|
-
// developer's current Claude Code usage percentage
|
|
19
|
-
// backend/src/features/claude-usage/README.md for why this is
|
|
17
|
+
// - report_claude_usage: POST /api/admin/claude-usage — SELF-ATTESTED:
|
|
18
|
+
// self-reports this developer's current Claude Code usage percentage
|
|
19
|
+
// (see backend/src/features/claude-usage/README.md for why this is
|
|
20
20
|
// self-reported rather than fetched from an Anthropic API), shown on
|
|
21
21
|
// the company-admin panel's "Claude Usage" tab.
|
|
22
|
+
// - report_claude_token_usage: POST /api/admin/claude-usage/token-breakdown
|
|
23
|
+
// — REAL, not self-attested: scans this developer's own local Claude
|
|
24
|
+
// Code transcripts (~/.claude/projects/**/*.jsonl — this server runs on
|
|
25
|
+
// the same machine) for a 7-day token/web-search/subagent breakdown. The
|
|
26
|
+
// other four bug-resolution tools above also trigger this automatically
|
|
27
|
+
// as a fire-and-forget side effect (see tryAutoReportTokenUsage) — "every
|
|
28
|
+
// task done in the bug reporter" keeps this fresh with zero extra asks.
|
|
22
29
|
// Thin wrapper: all the actual logic (auth, scoping, the DB writes) lives
|
|
23
30
|
// server-side; this just forwards the HTTP calls the README documents.
|
|
24
31
|
//
|
|
@@ -27,6 +34,9 @@
|
|
|
27
34
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
28
35
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
29
36
|
import { z } from 'zod';
|
|
37
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
38
|
+
import { join } from 'node:path';
|
|
39
|
+
import { homedir } from 'node:os';
|
|
30
40
|
|
|
31
41
|
const API_KEY = process.env.BUG_REPORT_API_KEY;
|
|
32
42
|
const BASE_URL = (process.env.BUG_REPORT_API_BASE_URL || 'https://api.zopkit.com').replace(/\/$/, '');
|
|
@@ -36,7 +46,7 @@ if (!API_KEY) {
|
|
|
36
46
|
'Generate one from the company-admin panel (Bug Reports → API Keys) and set it when registering this server.');
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.
|
|
49
|
+
const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.6.0' });
|
|
40
50
|
|
|
41
51
|
const BUG_SEVERITIES = ['low', 'medium', 'high', 'critical'];
|
|
42
52
|
// Keep in sync with BUG_STATUSES in backend/src/db/schema/platform/bug-reports.ts.
|
|
@@ -66,6 +76,194 @@ async function fetchImageContentBlock(url) {
|
|
|
66
76
|
return { type: 'image', data: buffer.toString('base64'), mimeType: contentType };
|
|
67
77
|
}
|
|
68
78
|
|
|
79
|
+
// ── Token usage breakdown (report_claude_token_usage) ──────────────────────
|
|
80
|
+
// Unlike usagePercent (self-attested, see submit_bug_resolution's comment on
|
|
81
|
+
// why), this is read straight off Claude Code's own local transcript files —
|
|
82
|
+
// this MCP server is a local process on the SAME machine as the Claude Code
|
|
83
|
+
// session using it, so it can read ~/.claude/projects/**/*.jsonl directly.
|
|
84
|
+
// No statusLine setup, no asking a human, no per-seat API that doesn't exist.
|
|
85
|
+
//
|
|
86
|
+
// Each API response gets logged 2-3x in these files (streaming writes the
|
|
87
|
+
// same final usage numbers once per content-block flush) — every count below
|
|
88
|
+
// dedupes by message.id, or totals would be inflated 2-3x.
|
|
89
|
+
const PROJECTS_DIR = join(homedir(), '.claude', 'projects');
|
|
90
|
+
const SUBAGENT_TOOL_NAMES = new Set(['Agent', 'Task']);
|
|
91
|
+
|
|
92
|
+
function listRecentTranscripts(sinceMs) {
|
|
93
|
+
const files = [];
|
|
94
|
+
let projectDirs;
|
|
95
|
+
try {
|
|
96
|
+
projectDirs = readdirSync(PROJECTS_DIR, { withFileTypes: true });
|
|
97
|
+
} catch {
|
|
98
|
+
return files; // no ~/.claude/projects at all — nothing to scan
|
|
99
|
+
}
|
|
100
|
+
for (const projectDir of projectDirs) {
|
|
101
|
+
if (!projectDir.isDirectory()) continue;
|
|
102
|
+
const dirPath = join(PROJECTS_DIR, projectDir.name);
|
|
103
|
+
let entries;
|
|
104
|
+
try {
|
|
105
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
106
|
+
} catch {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
|
|
111
|
+
const filePath = join(dirPath, entry.name);
|
|
112
|
+
try {
|
|
113
|
+
if (statSync(filePath).mtimeMs >= sinceMs) files.push(filePath);
|
|
114
|
+
} catch {
|
|
115
|
+
// file vanished between readdir and stat — skip
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return files;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** One session's token totals + what it used — null if the file had nothing
|
|
123
|
+
* in range (e.g. mtime was recent but every logged message predates sinceMs). */
|
|
124
|
+
function scanTranscript(filePath, sinceMs) {
|
|
125
|
+
let raw;
|
|
126
|
+
try {
|
|
127
|
+
raw = readFileSync(filePath, 'utf8');
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const seenMessageIds = new Set();
|
|
133
|
+
let inputTokens = 0, outputTokens = 0, cacheCreationTokens = 0, cacheReadTokens = 0;
|
|
134
|
+
let usedWebSearch = false, usedSubagents = false;
|
|
135
|
+
let startedAt = null, cwd = null;
|
|
136
|
+
|
|
137
|
+
for (const line of raw.split('\n')) {
|
|
138
|
+
if (!line.trim()) continue;
|
|
139
|
+
let entry;
|
|
140
|
+
try {
|
|
141
|
+
entry = JSON.parse(line);
|
|
142
|
+
} catch {
|
|
143
|
+
continue; // truncated/partial last line — skip it, not the whole file
|
|
144
|
+
}
|
|
145
|
+
if (entry.type !== 'assistant' || !entry.message) continue;
|
|
146
|
+
if (entry.timestamp && Date.parse(entry.timestamp) < sinceMs) continue;
|
|
147
|
+
|
|
148
|
+
if (cwd === null && entry.cwd) cwd = entry.cwd;
|
|
149
|
+
if (entry.timestamp && (!startedAt || entry.timestamp < startedAt)) startedAt = entry.timestamp;
|
|
150
|
+
|
|
151
|
+
// Tool-use detection runs on every line, duplicates included — setting a
|
|
152
|
+
// boolean true twice is harmless, unlike double-counting tokens below.
|
|
153
|
+
if (Array.isArray(entry.message.content)) {
|
|
154
|
+
for (const block of entry.message.content) {
|
|
155
|
+
if (block?.type === 'tool_use' && SUBAGENT_TOOL_NAMES.has(block.name)) usedSubagents = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const messageId = entry.message.id;
|
|
160
|
+
if (messageId) {
|
|
161
|
+
if (seenMessageIds.has(messageId)) continue;
|
|
162
|
+
seenMessageIds.add(messageId);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const usage = entry.message.usage;
|
|
166
|
+
if (!usage) continue;
|
|
167
|
+
inputTokens += usage.input_tokens || 0;
|
|
168
|
+
outputTokens += usage.output_tokens || 0;
|
|
169
|
+
cacheCreationTokens += usage.cache_creation_input_tokens || 0;
|
|
170
|
+
cacheReadTokens += usage.cache_read_input_tokens || 0;
|
|
171
|
+
const serverTools = usage.server_tool_use;
|
|
172
|
+
if (serverTools && ((serverTools.web_search_requests || 0) > 0 || (serverTools.web_fetch_requests || 0) > 0)) {
|
|
173
|
+
usedWebSearch = true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const totalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
|
|
178
|
+
if (totalTokens === 0) return null;
|
|
179
|
+
return { totalTokens, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens, usedWebSearch, usedSubagents, startedAt, cwd };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Scans every local Claude Code transcript touched in the last `days` days
|
|
183
|
+
* and aggregates it into the shape reportClaudeTokenUsageSchema expects.
|
|
184
|
+
* Deliberately NOT scoped to this repo/project — the whole point is "how
|
|
185
|
+
* much Claude Code usage this week", same scope /usage itself reports. */
|
|
186
|
+
function computeWeeklyTokenBreakdown(days = 7) {
|
|
187
|
+
const sinceMs = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
188
|
+
const sessions = listRecentTranscripts(sinceMs)
|
|
189
|
+
.map((f) => scanTranscript(f, sinceMs))
|
|
190
|
+
.filter((s) => s !== null);
|
|
191
|
+
|
|
192
|
+
const totals = sessions.reduce(
|
|
193
|
+
(acc, s) => ({
|
|
194
|
+
inputTokens: acc.inputTokens + s.inputTokens,
|
|
195
|
+
outputTokens: acc.outputTokens + s.outputTokens,
|
|
196
|
+
cacheCreationTokens: acc.cacheCreationTokens + s.cacheCreationTokens,
|
|
197
|
+
cacheReadTokens: acc.cacheReadTokens + s.cacheReadTokens,
|
|
198
|
+
}),
|
|
199
|
+
{ inputTokens: 0, outputTokens: 0, cacheCreationTokens: 0, cacheReadTokens: 0 }
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const topSessions = [...sessions]
|
|
203
|
+
.sort((a, b) => b.totalTokens - a.totalTokens)
|
|
204
|
+
.slice(0, 5)
|
|
205
|
+
.map((s) => ({
|
|
206
|
+
totalTokens: s.totalTokens,
|
|
207
|
+
startedAt: s.startedAt || new Date(sinceMs).toISOString(),
|
|
208
|
+
cwd: s.cwd,
|
|
209
|
+
usedWebSearch: s.usedWebSearch,
|
|
210
|
+
usedSubagents: s.usedSubagents,
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
periodStart: new Date(sinceMs).toISOString(),
|
|
215
|
+
periodEnd: new Date().toISOString(),
|
|
216
|
+
...totals,
|
|
217
|
+
sessionCount: sessions.length,
|
|
218
|
+
sessionsWithWebSearch: sessions.filter((s) => s.usedWebSearch).length,
|
|
219
|
+
sessionsWithSubagents: sessions.filter((s) => s.usedSubagents).length,
|
|
220
|
+
topSessions,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** "585k" / "2.3M" / "1.1B" — raw token counts in a long session routinely
|
|
225
|
+
* reach the hundreds of millions (every turn re-pays for its whole cached
|
|
226
|
+
* context, so cache-read alone compounds fast), so a plain toLocaleString()
|
|
227
|
+
* reads as an alarming wall of digits. Still real numbers — just formatted
|
|
228
|
+
* for a human skimming a report, not a ledger. */
|
|
229
|
+
function formatTokenCount(n) {
|
|
230
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
231
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
232
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(0)}k`;
|
|
233
|
+
return String(n);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** input + output + cache-WRITE tokens — genuinely new content processed
|
|
237
|
+
* this period. Deliberately excludes cache-READ: that's a heavily-discounted
|
|
238
|
+
* re-read of context already paid for once, and in a long session it
|
|
239
|
+
* dwarfs everything else by 10-100x, so folding it into one headline
|
|
240
|
+
* number would make "new work this week" unreadable. Still reported
|
|
241
|
+
* separately in full (see the schema) — just not what ranks/headlines. */
|
|
242
|
+
function newTokenCount(breakdown) {
|
|
243
|
+
return breakdown.inputTokens + breakdown.outputTokens + breakdown.cacheCreationTokens;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Fire-and-forget: scan + POST the breakdown, swallowing any failure (a
|
|
247
|
+
* missing ~/.claude/projects dir, a network blip, whatever) — this must
|
|
248
|
+
* never fail or slow down the tool call it's piggybacking on. Returns a
|
|
249
|
+
* short status string for an optional footer, or null if it didn't run. */
|
|
250
|
+
async function tryAutoReportTokenUsage() {
|
|
251
|
+
if (!API_KEY) return null;
|
|
252
|
+
try {
|
|
253
|
+
const breakdown = computeWeeklyTokenBreakdown();
|
|
254
|
+
if (breakdown.sessionCount === 0) return null;
|
|
255
|
+
const res = await fetch(`${BASE_URL}/api/admin/claude-usage/token-breakdown`, {
|
|
256
|
+
method: 'POST',
|
|
257
|
+
headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
|
|
258
|
+
body: JSON.stringify(breakdown),
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) return null;
|
|
261
|
+
return `${formatTokenCount(newTokenCount(breakdown))} new tokens across ${breakdown.sessionCount} session(s) this week`;
|
|
262
|
+
} catch {
|
|
263
|
+
return null; // never let this break the real tool call
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
69
267
|
server.registerTool(
|
|
70
268
|
'submit_bug_resolution',
|
|
71
269
|
{
|
|
@@ -116,12 +314,13 @@ server.registerTool(
|
|
|
116
314
|
}
|
|
117
315
|
|
|
118
316
|
const report = body?.data;
|
|
317
|
+
const usageNote = await tryAutoReportTokenUsage();
|
|
119
318
|
return {
|
|
120
319
|
content: [{
|
|
121
320
|
type: 'text',
|
|
122
|
-
text: report
|
|
321
|
+
text: (report
|
|
123
322
|
? `Resolution submitted. "${report.title}" is now ${report.status}.`
|
|
124
|
-
: 'Resolution submitted.',
|
|
323
|
+
: 'Resolution submitted.') + (usageNote ? `\n\n(Also updated your Claude token usage: ${usageNote}.)` : ''),
|
|
125
324
|
}],
|
|
126
325
|
};
|
|
127
326
|
}
|
|
@@ -209,6 +408,9 @@ server.registerTool(
|
|
|
209
408
|
}
|
|
210
409
|
}
|
|
211
410
|
|
|
411
|
+
const usageNote = await tryAutoReportTokenUsage();
|
|
412
|
+
if (usageNote) content.push({ type: 'text', text: `(Also updated your Claude token usage: ${usageNote}.)` });
|
|
413
|
+
|
|
212
414
|
return { content };
|
|
213
415
|
}
|
|
214
416
|
);
|
|
@@ -282,7 +484,13 @@ server.registerTool(
|
|
|
282
484
|
? `${reports.length} of ${meta.total} assigned report(s) (showing offset ${meta.offset}–${meta.offset + reports.length - 1}; pass a higher offset for more):`
|
|
283
485
|
: `${reports.length} assigned report(s):`;
|
|
284
486
|
|
|
285
|
-
|
|
487
|
+
const usageNote = await tryAutoReportTokenUsage();
|
|
488
|
+
return {
|
|
489
|
+
content: [{
|
|
490
|
+
type: 'text',
|
|
491
|
+
text: `${header}\n${summary}` + (usageNote ? `\n\n(Also updated your Claude token usage: ${usageNote}.)` : ''),
|
|
492
|
+
}],
|
|
493
|
+
};
|
|
286
494
|
}
|
|
287
495
|
);
|
|
288
496
|
|
|
@@ -332,12 +540,13 @@ server.registerTool(
|
|
|
332
540
|
}
|
|
333
541
|
|
|
334
542
|
const report = body?.data;
|
|
543
|
+
const usageNote = await tryAutoReportTokenUsage();
|
|
335
544
|
return {
|
|
336
545
|
content: [{
|
|
337
546
|
type: 'text',
|
|
338
|
-
text: report
|
|
547
|
+
text: (report
|
|
339
548
|
? `Bug report created: "${report.title}" (id: ${report.id}).`
|
|
340
|
-
: 'Bug report created.',
|
|
549
|
+
: 'Bug report created.') + (usageNote ? `\n\n(Also updated your Claude token usage: ${usageNote}.)` : ''),
|
|
341
550
|
}],
|
|
342
551
|
};
|
|
343
552
|
}
|
|
@@ -401,5 +610,68 @@ server.registerTool(
|
|
|
401
610
|
}
|
|
402
611
|
);
|
|
403
612
|
|
|
613
|
+
server.registerTool(
|
|
614
|
+
'report_claude_token_usage',
|
|
615
|
+
{
|
|
616
|
+
title: 'Report Claude token usage breakdown',
|
|
617
|
+
description:
|
|
618
|
+
"Scans this developer's own local Claude Code transcripts " +
|
|
619
|
+
'(~/.claude/projects/**/*.jsonl — this MCP server runs on the same machine, so it can read them ' +
|
|
620
|
+
'directly) and reports a real token-usage breakdown for the last 7 days: total tokens, how many ' +
|
|
621
|
+
'sessions, and how many of those sessions used web search or spawned a subagent. Unlike ' +
|
|
622
|
+
"report_claude_usage's percentage, nothing here is self-attested — every number is read off real " +
|
|
623
|
+
'local files, so call this freely with no arguments; it never needs to ask a human for anything. ' +
|
|
624
|
+
"The other bug-resolution tools already call this automatically as a side effect, so you usually " +
|
|
625
|
+
"don't need to call it directly — this exists for an explicit, on-demand check.",
|
|
626
|
+
inputSchema: {},
|
|
627
|
+
},
|
|
628
|
+
async () => {
|
|
629
|
+
if (!API_KEY) {
|
|
630
|
+
return {
|
|
631
|
+
isError: true,
|
|
632
|
+
content: [{ type: 'text', text: 'BUG_REPORT_API_KEY is not set on this MCP server — see README.md.' }],
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const breakdown = computeWeeklyTokenBreakdown();
|
|
637
|
+
if (breakdown.sessionCount === 0) {
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: 'text', text: 'No Claude Code transcripts found in the last 7 days under ~/.claude/projects — nothing to report.' }],
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
let res;
|
|
644
|
+
try {
|
|
645
|
+
res = await fetch(`${BASE_URL}/api/admin/claude-usage/token-breakdown`, {
|
|
646
|
+
method: 'POST',
|
|
647
|
+
headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
|
|
648
|
+
body: JSON.stringify(breakdown),
|
|
649
|
+
});
|
|
650
|
+
} catch (err) {
|
|
651
|
+
return {
|
|
652
|
+
isError: true,
|
|
653
|
+
content: [{ type: 'text', text: `Could not reach ${BASE_URL}: ${err instanceof Error ? err.message : String(err)}` }],
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const body = await res.json().catch(() => null);
|
|
658
|
+
|
|
659
|
+
if (!res.ok) {
|
|
660
|
+
const message = body?.message || body?.error || `HTTP ${res.status}`;
|
|
661
|
+
return { isError: true, content: [{ type: 'text', text: `Failed to report Claude token usage: ${message}` }] };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
return {
|
|
665
|
+
content: [{
|
|
666
|
+
type: 'text',
|
|
667
|
+
text: `Reported ${formatTokenCount(newTokenCount(breakdown))} new tokens ` +
|
|
668
|
+
`(plus ${formatTokenCount(breakdown.cacheReadTokens)} discounted cache-read re-reads) ` +
|
|
669
|
+
`across ${breakdown.sessionCount} session(s) this week ` +
|
|
670
|
+
`(${breakdown.sessionsWithWebSearch} used web search, ${breakdown.sessionsWithSubagents} used subagents).`,
|
|
671
|
+
}],
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
);
|
|
675
|
+
|
|
404
676
|
const transport = new StdioServerTransport();
|
|
405
677
|
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.6.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, file new bug reports,
|
|
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, report your Claude Code usage status, and report a real weekly token-usage breakdown from your local transcripts. 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"
|