@theronap/cortex-mcp 0.9.74 → 0.9.75

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/lib/diagnose.mjs CHANGED
@@ -215,6 +215,41 @@ export async function checkToken(token, base) {
215
215
  return { ok: true, status: 200, projectCount, requestId }
216
216
  }
217
217
 
218
+ // Probe the org-skills surface. SEPARATE FROM checkToken ON PURPOSE.
219
+ //
220
+ // WHY THIS EXISTS. `doctor` checked exactly one endpoint — /api/mcp-context — and reported PASS on
221
+ // the strength of it. Meanwhile /api/skills answered 409 to every multi-brain caller for three days
222
+ // (measured 2026-08-08: 14 requests, 14 × 409, zero successes; the local cache sat frozen from Aug 5
223
+ // to Aug 8). Org-published skills silently never installed, the client swallowed the status, and
224
+ // doctor — the tool setup.txt tells people to run FIRST when something is wrong — said everything
225
+ // was fine, every single time. A check that probes one surface is a health check for that surface,
226
+ // not for the system, and must not be reported as the latter.
227
+ //
228
+ // Returns checkToken's shape so the caller renders both the same way. `skillCount` is
229
+ // informational: an org with zero published skills is perfectly healthy, so it is NOT a failure.
230
+ export async function checkSkills(token, base) {
231
+ if (!token || !isUuid(token)) {
232
+ return { ok: false, status: 0, diagnosis: { kind: 'config', retriable: false,
233
+ message: 'no usable token — the context check above already covers this' } }
234
+ }
235
+ const url = `${(base ?? CANONICAL_BASE).replace(/\/$/, '')}/api/skills`
236
+ let res
237
+ try {
238
+ res = await fetchCortex(url, { headers: { Authorization: `Bearer ${token}` } })
239
+ } catch (e) {
240
+ return { ok: false, status: 0, diagnosis: { kind: 'network', retriable: true, message: e.message } }
241
+ }
242
+ const requestId = res.headers.get('x-vercel-id') ?? null
243
+ const contentType = res.headers.get('content-type')
244
+ const body = await res.text()
245
+ if (!res.ok) {
246
+ return { ok: false, status: res.status, requestId, diagnosis: classify(res.status, contentType, body, requestId) }
247
+ }
248
+ let skillCount
249
+ try { skillCount = (JSON.parse(body)?.skills ?? []).length } catch { /* shape drift — non-fatal */ }
250
+ return { ok: true, status: 200, skillCount, requestId }
251
+ }
252
+
218
253
  // Release the keep-alive sockets the global fetch (undici) holds, so a short-lived CLI command
219
254
  // can tear down cleanly. Without this, calling process.exit() right after a fetch can race a
220
255
  // lingering socket handle and trip a libuv assertion on Windows
package/lib/doctor.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, existsSync } from 'fs'
2
2
  import { homedir } from 'os'
3
3
  import { join } from 'path'
4
- import { checkToken, resolveBase } from './diagnose.mjs'
4
+ import { checkToken, checkSkills, resolveBase } from './diagnose.mjs'
5
5
 
6
6
  // `npx @theronap/cortex-mcp doctor` — a live, one-command health check.
7
7
  //
@@ -64,9 +64,33 @@ export async function runDoctor() {
64
64
  const r = await checkToken(token, base)
65
65
 
66
66
  if (r.ok) {
67
- out(' ✓ PASS — your token authenticates and Agnoclast returned your context.')
67
+ out(' ✓ context — your token authenticates and Agnoclast returned your context.')
68
68
  if (typeof r.projectCount === 'number') out(` You can currently see ${r.projectCount} project(s).`)
69
69
  if (r.requestId) out(` (request id: ${r.requestId})`)
70
+
71
+ // A SECOND surface, because one endpoint answering is not "the system is healthy". /api/skills
72
+ // 409'd every multi-brain caller for three days while this command printed PASS on the strength
73
+ // of /api/mcp-context alone. See checkSkills' header for the full record.
74
+ const s = await checkSkills(token, base)
75
+ if (s.ok) {
76
+ const n = typeof s.skillCount === 'number'
77
+ ? ` (${s.skillCount} org skill${s.skillCount === 1 ? '' : 's'} published)` : ''
78
+ out(` ✓ skills — the org-skill surface answers${n}.`)
79
+ } else {
80
+ // NOT a total failure — context works, so capture and retrieval are fine. But it must never
81
+ // again render as PASS, because org skills silently stop updating when this breaks.
82
+ out(` ✗ skills — ${s.diagnosis?.kind ?? 'error'}${s.status ? ` (HTTP ${s.status})` : ''}`)
83
+ out(` ${s.diagnosis?.message ?? 'unknown error'}`)
84
+ out('')
85
+ out(' ⚠ PARTIAL — your connection is fine and capture/retrieval work, but org-published')
86
+ out(' skills cannot be fetched, so they will silently stop updating (the client falls')
87
+ out(' back to its last-good cache). Report this rather than ignoring it.')
88
+ out('')
89
+ return 1
90
+ }
91
+
92
+ out('')
93
+ out(' ✓ PASS — every surface checked is answering.')
70
94
  out('')
71
95
  out(' If your AI still does not see Agnoclast, the server just is not loaded yet —')
72
96
  out(' fully quit and reopen Claude Code (the MCP server starts on launch).')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.74",
3
+ "version": "0.9.75",
4
4
  "description": "Connect your AI assistant to Cortex — your org's projects, activity, gaps, and directives, scoped to you.",
5
5
  "type": "module",
6
6
  "bin": {