@theronap/cortex-mcp 0.9.20 → 0.9.22

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 (2) hide show
  1. package/lib/server.mjs +54 -2
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -213,13 +213,32 @@ export async function runServer(version) {
213
213
  'project_status',
214
214
  {
215
215
  title: 'Project status',
216
- description: 'Status of a specific project by key (e.g. checkout-v2). Returns only what you can see.',
216
+ description: 'Status of a specific project by key (e.g. checkout-v2). Returns the AUTHORED wiki page (the synthesized understanding) when one exists, falling back to the records-derived line. Returns only what you can see.',
217
217
  inputSchema: { key: z.string().describe('project key, e.g. checkout-v2') },
218
218
  },
219
219
  async ({ key }) => {
220
+ // Prefer the AUTHORED page (synthesized understanding, sovereign over records). Freshest tier leads,
221
+ // each dated, so diverged tiers read as "newest first" instead of an undated stale/fresh blend.
222
+ try {
223
+ const res = await fetchCortex(`${BASE}/api/brain/page?kind=project&key=${encodeURIComponent(key)}`,
224
+ { headers: { Authorization: `Bearer ${TOKEN}` } })
225
+ if (res.ok) {
226
+ const page = await res.json()
227
+ if (page?.authored && Array.isArray(page.tiers) && page.tiers.length) {
228
+ const day = (d) => (d ? String(d).slice(0, 10) : '')
229
+ const blocks = page.tiers.map((t) => {
230
+ const secs = (t.sections ?? []).map((s) => `### ${s.heading}\n${s.body}`).join('\n\n')
231
+ const head = `[${t.tier}${day(t.updated_at) ? ` · authored ${day(t.updated_at)}` : ''}${t.validity && t.validity !== 'current' ? ` · ${t.validity}` : ''}]`
232
+ return [head, t.summary, secs].filter(Boolean).join('\n')
233
+ })
234
+ return { content: [{ type: 'text', text: `# ${page.title ?? key} (authored page)\n\n${blocks.join('\n\n---\n\n')}` }] }
235
+ }
236
+ }
237
+ } catch { /* fall through to the records-derived line */ }
238
+ // Fallback: no authored page yet — surface the records-derived line from the context snapshot.
220
239
  const text = await fetchContext()
221
240
  const line = text.split('\n').find((l) => l.includes(`**${key}**`) || l.includes(key))
222
- return { content: [{ type: 'text', text: line ? `Project ${key}:\n${line.trim()}` : `No visible project "${key}".` }] }
241
+ return { content: [{ type: 'text', text: line ? `Project ${key} (records-derived; not yet authored):\n${line.trim()}` : `No visible project "${key}".` }] }
223
242
  },
224
243
  )
225
244
 
@@ -464,6 +483,39 @@ export async function runServer(version) {
464
483
  },
465
484
  )
466
485
 
486
+ server.registerTool(
487
+ 'set_page_validity',
488
+ {
489
+ title: 'Mark a wiki page current / superseded / historical',
490
+ description: 'Set the CURRENCY of one of YOUR OWN authored pages so the brain can filter out stale info: "current" (reflects reality now — the default), "superseded" (replaced by a newer page — pass superseded_by with that page\'s name), or "historical" (kept for the record, no longer current). Optionally set modality: "reality" (as-built), "plan" (intended, not yet real), or "construction" (being built now). Call this the moment your understanding changes that a page is no longer the live truth — e.g. right after you author the new reality, mark the old page superseded. Owner-only.',
491
+ inputSchema: {
492
+ kind: z.enum(['project', 'person', 'org', 'user']).describe('the page kind'),
493
+ name: z.string().describe('the exact page name'),
494
+ validity: z.enum(['current', 'superseded', 'historical']).describe('the currency state'),
495
+ modality: z.enum(['reality', 'plan', 'construction']).optional().describe('what the page describes (optional)'),
496
+ superseded_by: z.string().optional().describe('for superseded: the name of the page that replaced it'),
497
+ },
498
+ },
499
+ async ({ kind, name, validity, modality, superseded_by }) => {
500
+ let res
501
+ try {
502
+ res = await fetchCortex(`${BASE}/api/brain/validity`, {
503
+ method: 'POST',
504
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
505
+ body: JSON.stringify({ kind, name, validity, modality, superseded_by }),
506
+ })
507
+ } catch (e) {
508
+ return { content: [{ type: 'text', text: `Could not set validity: ${e.message}` }] }
509
+ }
510
+ if (!res.ok) {
511
+ const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
512
+ return { content: [{ type: 'text', text: `Could not set validity: ${d.message}` }] }
513
+ }
514
+ const out = await res.json()
515
+ return { content: [{ type: 'text', text: `Done — "${out.name}" is now ${out.validity}${out.modality ? ` / ${out.modality}` : ''} (${out.docs_updated} tier-doc(s) updated).` }] }
516
+ },
517
+ )
518
+
467
519
  // ── File requests: ask the owner for a record's FULL original (lives on their machine) ──
468
520
  const fail = (verb, res) => async () =>
469
521
  ({ content: [{ type: 'text', text: `Could not ${verb}: ${classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id')).message}` }] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.20",
3
+ "version": "0.9.22",
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": {