@theronap/cortex-mcp 0.9.37 → 0.9.38
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/server.mjs +63 -31
- package/package.json +1 -1
package/lib/server.mjs
CHANGED
|
@@ -167,21 +167,27 @@ export async function runServer(version) {
|
|
|
167
167
|
const body = await res.text()
|
|
168
168
|
throw new Error(classify(res.status, res.headers.get('content-type'), body, res.headers.get('x-vercel-id')).message)
|
|
169
169
|
}
|
|
170
|
-
const { people = [], records = [] } = await res.json()
|
|
170
|
+
const { people = [], records = [], brains = [] } = await res.json()
|
|
171
171
|
if (!people.length && !records.length) return { content: [{ type: 'text', text: `No visible results for "${query}".` }] }
|
|
172
|
+
// Multi-brain: tag each hit with its brain only when results span more than one (single-brain
|
|
173
|
+
// callers see no noise). `brains` also carries any brain that errored (fail-soft).
|
|
174
|
+
const multi = brains.filter((b) => b.ok).length > 1
|
|
175
|
+
const tag = (brain) => (multi && brain ? ` · ${brain}` : '')
|
|
172
176
|
const lines = []
|
|
173
177
|
if (people.length) {
|
|
174
178
|
lines.push('People:')
|
|
175
179
|
for (const p of people) {
|
|
176
180
|
const meta = [p.title, p.company].filter(Boolean).join(', ')
|
|
177
|
-
lines.push(`- ${p.name}${meta ? ` (${meta})` : ''} — mentioned in ${p.mentions} record${p.mentions === 1 ? '' : 's'}`)
|
|
181
|
+
lines.push(`- ${p.name}${meta ? ` (${meta})` : ''} — mentioned in ${p.mentions} record${p.mentions === 1 ? '' : 's'}${tag(p.brain)}`)
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
if (records.length) {
|
|
181
185
|
if (lines.length) lines.push('')
|
|
182
186
|
lines.push('Activity:')
|
|
183
|
-
for (const r of records) lines.push(`- [${r.source}] ${r.title}${r.project ? ` (${r.project})` : ''}`)
|
|
187
|
+
for (const r of records) lines.push(`- [${r.source}] ${r.title}${r.project ? ` (${r.project})` : ''}${tag(r.brain)}`)
|
|
184
188
|
}
|
|
189
|
+
const failed = brains.filter((b) => !b.ok)
|
|
190
|
+
if (failed.length) lines.push('', `(couldn't reach ${failed.length} brain${failed.length === 1 ? '' : 's'}: ${failed.map((b) => b.name).join(', ')})`)
|
|
185
191
|
return { content: [{ type: 'text', text: `Results for "${query}":\n${lines.join('\n')}` }] }
|
|
186
192
|
},
|
|
187
193
|
)
|
|
@@ -224,14 +230,24 @@ export async function runServer(version) {
|
|
|
224
230
|
{ headers: { Authorization: `Bearer ${TOKEN}` } })
|
|
225
231
|
if (res.ok) {
|
|
226
232
|
const page = await res.json()
|
|
227
|
-
|
|
233
|
+
// Multi-brain: /api/brain/page returns matches[] (one per brain that has the page).
|
|
234
|
+
let matches = Array.isArray(page?.matches) ? page.matches.filter((m) => m.authored && Array.isArray(m.tiers) && m.tiers.length) : []
|
|
235
|
+
// Rollout back-compat: adapt an older server's single-page shape to one match.
|
|
236
|
+
if (!matches.length && page?.authored && Array.isArray(page.tiers) && page.tiers.length) {
|
|
237
|
+
matches = [{ brain: null, authored: true, title: page.title, tiers: page.tiers }]
|
|
238
|
+
}
|
|
239
|
+
if (matches.length) {
|
|
228
240
|
const day = (d) => (d ? String(d).slice(0, 10) : '')
|
|
229
|
-
const
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
241
|
+
const renderMatch = (m) => {
|
|
242
|
+
const blocks = m.tiers.map((t) => {
|
|
243
|
+
const secs = (t.sections ?? []).map((s) => `### ${s.heading}\n${s.body}`).join('\n\n')
|
|
244
|
+
const head = `[${t.tier}${day(t.updated_at) ? ` · authored ${day(t.updated_at)}` : ''}${t.validity && t.validity !== 'current' ? ` · ${t.validity}` : ''}]`
|
|
245
|
+
return [head, t.summary, secs].filter(Boolean).join('\n')
|
|
246
|
+
})
|
|
247
|
+
const brainTag = matches.length > 1 ? ` · brain: ${m.brain}` : ''
|
|
248
|
+
return `# ${m.title ?? key} (authored page${brainTag})\n\n${blocks.join('\n\n---\n\n')}`
|
|
249
|
+
}
|
|
250
|
+
return { content: [{ type: 'text', text: matches.map(renderMatch).join('\n\n═══\n\n') }] }
|
|
235
251
|
}
|
|
236
252
|
}
|
|
237
253
|
} catch { /* fall through to the records-derived line */ }
|
|
@@ -345,29 +361,45 @@ export async function runServer(version) {
|
|
|
345
361
|
return { content: [{ type: 'text', text: `Could not read "${name}": ${d.message}` }] }
|
|
346
362
|
}
|
|
347
363
|
const page = await res.json()
|
|
348
|
-
|
|
349
|
-
|
|
364
|
+
// Multi-brain (decision 3A): /api/brain/page returns matches[] — one entry per brain that has a
|
|
365
|
+
// page under this name. One match (the common case) reads exactly as before, plus a brain tag only
|
|
366
|
+
// when >1 brain matches — never silently one brain's version.
|
|
367
|
+
let matches = Array.isArray(page?.matches) ? page.matches.filter((m) => m.authored && Array.isArray(m.tiers) && m.tiers.length) : []
|
|
368
|
+
// Rollout back-compat: an older server returns the single-page shape ({authored, tiers}) with no
|
|
369
|
+
// matches[]; adapt it to one match so this build works against both old and new deployments.
|
|
370
|
+
if (!matches.length && page?.authored && Array.isArray(page.tiers) && page.tiers.length) {
|
|
371
|
+
matches = [{ brain: null, authored: true, ref: page.ref, title: page.title, tiers: page.tiers }]
|
|
372
|
+
}
|
|
373
|
+
if (!matches.length) {
|
|
374
|
+
return { content: [{ type: 'text', text: `No authored ${k} page named "${name}" in any of your brains. If it's a person or team, pass kind (person/org). Otherwise \`grep "${name}"\` to locate it — it may be a red-link (a wanted page that isn't authored yet).` }] }
|
|
350
375
|
}
|
|
351
376
|
const day = (d) => (d ? String(d).slice(0, 10) : '')
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
377
|
+
const renderMatch = (m, tagBrain) => {
|
|
378
|
+
const blocks = m.tiers.map((t) => {
|
|
379
|
+
const secs = (t.sections ?? []).map((s) => `### ${s.heading}\n${s.body}`).join('\n\n')
|
|
380
|
+
// ADR-0018: a null version isn't "nothing to show" — it means this variant predates content-
|
|
381
|
+
// hash tracking (a 2026-06-29 import scar) and CANNOT be re-authored via base_version until an
|
|
382
|
+
// admin backfills it. Silently omitting the line here is exactly what sent callers into an
|
|
383
|
+
// unrecoverable base_version guessing loop; say so explicitly instead.
|
|
384
|
+
const versionLine = t.version
|
|
385
|
+
? `\nversion: ${t.version}`
|
|
386
|
+
: `\nversion: none (this variant predates content-hash tracking — base_version writes will always fail here; ask an admin about the ADR-0018 backfill)`
|
|
387
|
+
const head = `[${t.tier}${day(t.updated_at) ? ` · authored ${day(t.updated_at)}` : ''}${t.validity && t.validity !== 'current' ? ` · ${t.validity}` : ''}${versionLine}]`
|
|
388
|
+
return [head, t.summary, secs].filter(Boolean).join('\n')
|
|
389
|
+
})
|
|
390
|
+
let footer = `— Follow any [[links]] above with read_page to go deeper.\n— If you hold fresher FIRST-HAND truth than this page — something you established THIS session (ran the command, made the change, hold the artifact) that a section here gets concretely wrong — re-author just those sections now with \`author\` (any node you are permitted to edit; concise; a material contradiction, not merely extra detail). Pass this page's \`version\` as base_version when you re-author — if the save comes back stale, the page changed under you: re-read, then re-author. Reading a stale page you can fix IS the trigger to fix it.`
|
|
391
|
+
// slice 4: when the page carries identifier stamps, the history projection is one flag away.
|
|
392
|
+
const allBody = m.tiers.flatMap((t) => (t.sections ?? []).map((s) => s.body)).join('\n')
|
|
393
|
+
const stamps = [...new Set((allBody.match(/\[\[repo:[a-z0-9][a-z0-9-]*\/[a-z0-9_.-]+\]\]/gi) ?? []).map((s) => s.toLowerCase()))]
|
|
394
|
+
if (stamps.length) footer += `\n— This page carries ${stamps.join(', ')} — \`read_page "${name}"\` with history: true for its event timeline (page = present, timeline = history).`
|
|
395
|
+
const brainTag = tagBrain ? ` · brain: ${m.brain}` : ''
|
|
396
|
+
return `# ${m.title ?? name} (full authored page${brainTag})\n\n${blocks.join('\n\n---\n\n')}\n\n${footer}`
|
|
397
|
+
}
|
|
398
|
+
if (matches.length === 1) {
|
|
399
|
+
return { content: [{ type: 'text', text: renderMatch(matches[0], false) }] }
|
|
400
|
+
}
|
|
401
|
+
const header = `"${name}" is authored in ${matches.length} of your brains — all shown (each tagged with its brain, newest tier first):`
|
|
402
|
+
return { content: [{ type: 'text', text: [header, ...matches.map((m) => renderMatch(m, true))].join('\n\n═══════════════════\n\n') }] }
|
|
371
403
|
},
|
|
372
404
|
)
|
|
373
405
|
|