@theronap/cortex-mcp 0.9.27 → 0.9.28

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 +32 -2
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -252,10 +252,35 @@ export async function runServer(version) {
252
252
  name: z.string().describe('the canonical node name exactly as written (e.g. "Cortex", "Ben", or a [[link]] target) — identifier links ([[repo:owner/name]]) resolve to their authored HOME + a visible-event count'),
253
253
  kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('node kind (default project; pass person/org for people/teams)'),
254
254
  expand: z.boolean().optional().describe('identifier names only: also list recent visible timeline events for this identifier (default: home + count)'),
255
+ history: z.boolean().optional().describe('node names only: return the node\'s TIMELINE (events joined via its [[repo:…]] stamps, reverse-chron, viewer-visible) instead of the page body. The page is the present; this is the history.'),
255
256
  },
256
257
  },
257
- async ({ name, kind, expand }) => {
258
+ async ({ name, kind, expand, history }) => {
258
259
  const k = kind ?? 'project'
260
+ // PER-NODE TIMELINE (slice 4): history = the projection over the node's identifier stamps.
261
+ if (history && !/^[a-z][a-z0-9_-]*:.+/i.test(name)) {
262
+ try {
263
+ const qs = new URLSearchParams({ kind: k, key: name })
264
+ const r = await fetchCortex(`${BASE}/api/node/timeline?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
265
+ if (r.status === 404) return { content: [{ type: 'text', text: `No ${k} named "${name}" — cannot project a timeline.` }] }
266
+ if (!r.ok) {
267
+ const d = classify(r.status, r.headers.get('content-type'), await r.text(), r.headers.get('x-vercel-id'))
268
+ return { content: [{ type: 'text', text: `Could not read the timeline for "${name}": ${d.message}` }] }
269
+ }
270
+ const t = await r.json()
271
+ if (!t.identifiers?.length) {
272
+ return { content: [{ type: 'text', text: `"${name}" carries no identifier stamps yet — no history joins. Stamp its page with [[repo:owner/name]] (what it identifies) and events will accrue here.` }] }
273
+ }
274
+ const lines = [`# ${name} — node timeline (history; the page is the present)`]
275
+ lines.push(`Joins: ${t.identifiers.map((i) => `[[${i.id}]] · ${i.visibleCount} visible`).join(' | ')}${t.incomplete ? ' (partial — one join failed to read)' : ''}`)
276
+ for (const e of t.events) lines.push(`- ${String(e.occurredAt).slice(0, 10)} · ${e.source} · ${e.summary}`)
277
+ if (!t.events.length) lines.push('(no events visible to you yet on these joins)')
278
+ if (t.siblings?.length) lines.push(`Sibling homes (share a stamp — bridges, not history): ${t.siblings.map((s) => `"${s.title}"`).join(', ')}`)
279
+ return { content: [{ type: 'text', text: lines.join('\n') }] }
280
+ } catch (e) {
281
+ return { content: [{ type: 'text', text: `Could not read the timeline for "${name}": ${e.message}` }] }
282
+ }
283
+ }
259
284
  // IDENTIFIER RESOLUTION (slice 3): an identifier-shaped name is a JOIN KEY, not a page — resolve
260
285
  // it to its authored home + a viewer-honest event count instead of 404ing. Loose shape-detect here;
261
286
  // the server enforces the strict canonical law (identifiers.ts) and 400s malformed forms with an
@@ -311,7 +336,12 @@ export async function runServer(version) {
311
336
  const head = `[${t.tier}${day(t.updated_at) ? ` · authored ${day(t.updated_at)}` : ''}${t.validity && t.validity !== 'current' ? ` · ${t.validity}` : ''}${t.version ? `\nversion: ${t.version}` : ''}]`
312
337
  return [head, t.summary, secs].filter(Boolean).join('\n')
313
338
  })
314
- const 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.`
339
+ 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.`
340
+ // slice 4: when the page carries identifier stamps, the history projection is one flag away.
341
+ // (client-side shape check — the acknowledged registry copy of identifiers.ts)
342
+ const allBody = page.tiers.flatMap((t) => (t.sections ?? []).map((s) => s.body)).join('\n')
343
+ const stamps = [...new Set((allBody.match(/\[\[repo:[a-z0-9][a-z0-9-]*\/[a-z0-9_.-]+\]\]/gi) ?? []).map((s) => s.toLowerCase()))]
344
+ if (stamps.length) footer += `\n— This page carries ${stamps.join(', ')} — \`read_page "${name}"\` with history: true for its event timeline (page = present, timeline = history).`
315
345
  return { content: [{ type: 'text', text: `# ${page.title ?? name} (full authored page)\n\n${blocks.join('\n\n---\n\n')}\n\n${footer}` }] }
316
346
  },
317
347
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.27",
3
+ "version": "0.9.28",
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": {