zopkit-bug-resolution-mcp 1.8.0 → 1.9.1

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.
@@ -35,7 +35,7 @@
35
35
  import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
36
36
  import { join } from 'node:path';
37
37
  import { homedir, tmpdir } from 'node:os';
38
- import { loadConfig, post, readStdinJson } from '../lib/zopkit-config.js';
38
+ import { loadConfig, post, readStdinJson, readClaudeAccount, reporterVersion } from '../lib/zopkit-config.js';
39
39
 
40
40
  // Per-session throttle. The status line fires on every assistant message; a
41
41
  // 40-turn session would otherwise be 40 POSTs. 15s keeps the panel's 10s
@@ -94,6 +94,9 @@ function buildHeartbeat(p, cfg, fresh) {
94
94
  sessionName: cfg.shareSessionName ? (p.session_name ?? null) : null,
95
95
  model: p.model?.id ?? null,
96
96
  version: p.version ?? null,
97
+ // This reporter's version, so the server can tell "old client" apart
98
+ // from "nothing to report" — they produce identical rows otherwise.
99
+ reporterVersion: reporterVersion(),
97
100
 
98
101
  contextTokens: p.context_window?.total_input_tokens ?? null,
99
102
  contextLimit: p.context_window?.context_window_size ?? null,
@@ -112,6 +115,10 @@ function buildHeartbeat(p, cfg, fresh) {
112
115
 
113
116
  rateLimits: p.rate_limits ?? null,
114
117
 
118
+ // Which Anthropic account this session is signed in to. Read from
119
+ // ~/.claude.json rather than the payload, which does not carry it.
120
+ account: readClaudeAccount(),
121
+
115
122
  // Whether a NEW API response landed since the last heartbeat for this
116
123
  // session, detected by the context token count moving. Only a fresh
117
124
  // reading may advance the account-level percentages: an idle session's
package/index.js CHANGED
@@ -91,7 +91,7 @@ if (!API_KEY) {
91
91
  'Generate one from the company-admin panel (Bug Reports → API Keys) and set it when registering this server.');
92
92
  }
93
93
 
94
- const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.8.0' });
94
+ const server = new McpServer({ name: 'zopkit-bug-resolution', version: '1.9.1' });
95
95
 
96
96
  const BUG_SEVERITIES = ['low', 'medium', 'high', 'critical'];
97
97
  // Keep in sync with BUG_STATUSES in backend/src/db/schema/platform/bug-reports.ts.
@@ -139,18 +139,28 @@ const SUBAGENT_TOOL_NAMES = new Set(['Agent', 'Task']);
139
139
  // weeklySessionsWithWebSearch; missing this half undercounted it before.
140
140
  const WEB_TOOL_NAMES = new Set(['WebSearch', 'WebFetch']);
141
141
 
142
- // Context-window ceiling per model family — used only to turn
143
- // currentContextTokens into a percentage the panel can render as a bar.
144
- // Heuristic, not queried from Anthropic (there's no local API for "is this
145
- // account's extended-context beta on"): Sonnet ships with a 1M-token beta
146
- // context window in Claude Code; Opus/Haiku are the standard 200k. Falls
147
- // back to 200k for anything unrecognized — an undercount of the true limit
148
- // is the safe failure direction (a 1M-context Opus session would just show
149
- // as "over 100%", visibly wrong and prompting a fix, not silently hidden).
142
+ // Context-window ceiling per model, used only to turn currentContextTokens
143
+ // into a percentage. A GUESS, and unavoidably so: the transcript records no
144
+ // window size (only context_management), so unlike the status-line path —
145
+ // which is handed context_window.context_window_size by Claude Code itself
146
+ // — this has to infer it.
147
+ //
148
+ // The previous rule was "sonnet => 1M, everything else => 200k", which put
149
+ // every Opus 5 session at a fifth of its real ceiling: a session the status
150
+ // line measured at 626k/1.0M (63%) showed here as 626k/200k, pinned red at
151
+ // 100%. The 5-series ships the 1M window; 4.x is 200k.
152
+ //
153
+ // Note the version is parsed positionally, not by substring: the family
154
+ // version is the segment right after the name, so claude-haiku-4-5-20251001
155
+ // is a 4-series despite containing "-5-".
150
156
  function contextLimitForModel(model) {
151
157
  if (!model) return 200_000;
152
- if (model.includes('sonnet')) return 1_000_000;
153
- return 200_000;
158
+ const [, version] = model.replace(/^claude-/, '').split('-');
159
+ const major = Number.parseInt(version, 10);
160
+ // Unrecognised => 200k. Undercounting the ceiling shows a session as
161
+ // fuller than it is, which is visibly wrong and gets reported; guessing
162
+ // high would quietly hide a session that is genuinely about to run out.
163
+ return Number.isFinite(major) && major >= 5 ? 1_000_000 : 200_000;
154
164
  }
155
165
 
156
166
  /**
@@ -16,11 +16,76 @@
16
16
  // Create it with: npx zopkit-bug-resolution-mcp --init-usage-reporting
17
17
 
18
18
  import { readFileSync } from 'node:fs';
19
- import { join } from 'node:path';
19
+ import { join, dirname } from 'node:path';
20
20
  import { homedir } from 'node:os';
21
+ import { fileURLToPath } from 'node:url';
21
22
 
22
23
  export const CONFIG_PATH = join(homedir(), '.claude', '.zopkit-usage.json');
23
24
 
25
+ /**
26
+ * This reporter's own version, sent with every heartbeat.
27
+ *
28
+ * The server cannot otherwise tell an OLD reporter apart from a machine
29
+ * with nothing to report: a pre-1.9.0 client sends perfect session rows
30
+ * and silently omits the account, which looks identical to a machine that
31
+ * has no signed-in Claude account. Both show up as "not reporting" and
32
+ * need completely different fixes.
33
+ *
34
+ * Read from package.json rather than hardcoded, so it cannot drift from
35
+ * the published version the way a duplicated constant would.
36
+ */
37
+ let versionCache;
38
+ export function reporterVersion() {
39
+ if (versionCache !== undefined) return versionCache;
40
+ try {
41
+ const here = dirname(fileURLToPath(import.meta.url));
42
+ versionCache = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')).version ?? null;
43
+ } catch {
44
+ versionCache = null;
45
+ }
46
+ return versionCache;
47
+ }
48
+
49
+ /**
50
+ * Which Anthropic account this machine is signed in to.
51
+ *
52
+ * Read from ~/.claude.json's oauthAccount block — the same file Claude
53
+ * Code keeps its own settings in. Nothing here is a credential: the uuid
54
+ * is an identifier, and the email/org are what the account calls itself.
55
+ *
56
+ * It matters because RATE LIMITS ARE PER ACCOUNT. The wrapper staff member
57
+ * reading the panel and the Claude account being billed need not be the
58
+ * same identity (on the machine this was written, they are not), and if
59
+ * two developers share one account then "this developer is at 70%" is the
60
+ * wrong sentence — the account is, and the token split says who drove it.
61
+ *
62
+ * Cached for the process: a status line runs on every assistant message
63
+ * and this file is ~MBs, so re-reading it per render would be the most
64
+ * expensive thing the reporter does.
65
+ */
66
+ let accountCache;
67
+ export function readClaudeAccount() {
68
+ if (accountCache !== undefined) return accountCache;
69
+ try {
70
+ const raw = JSON.parse(readFileSync(join(homedir(), '.claude.json'), 'utf8'));
71
+ const a = raw?.oauthAccount;
72
+ accountCache = a?.accountUuid
73
+ ? {
74
+ uuid: a.accountUuid,
75
+ email: a.emailAddress ?? null,
76
+ orgName: a.organizationName ?? null,
77
+ // The rate-limit TIER, not the seat: it is what explains why two
78
+ // accounts have different ceilings, so a high percentage on a
79
+ // small plan is not read as the same signal as one on a 20x.
80
+ plan: a.organizationRateLimitTier ?? a.organizationType ?? null,
81
+ }
82
+ : null;
83
+ } catch {
84
+ accountCache = null; // never signed in, or an unreadable file
85
+ }
86
+ return accountCache;
87
+ }
88
+
24
89
  let cached;
25
90
 
26
91
  export function loadConfig() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zopkit-bug-resolution-mcp",
3
- "version": "1.8.0",
3
+ "version": "1.9.1",
4
4
  "type": "module",
5
5
  "description": "MCP server + status-line/hook reporters 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 REAL Claude Code usage \u2014 live per-session context and account rate limits via the status line, plus per-bug token attribution via PostToolUse hooks. Requires a BUG_REPORT_API_KEY (generated in the company-admin panel) \u2014 see README.md.",
6
6
  "license": "UNLICENSED",