@theronap/cortex-mcp 0.9.36 → 0.9.37

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 +87 -1
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -253,10 +253,28 @@ export async function runServer(version) {
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
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.'),
256
+ version: z.string().optional().describe('read a HISTORICAL version of this page instead of the current one: a rev_no (e.g. "3") or a content_hash from page_history. Use page_history first to see the versions, then rollback_page to restore one.'),
256
257
  },
257
258
  },
258
- async ({ name, kind, expand, history }) => {
259
+ async ({ name, kind, expand, history, version }) => {
259
260
  const k = kind ?? 'project'
261
+ // PAGE HISTORY (Part A): a specific version reads one historical body, resolved within this page's
262
+ // own history. Not for identifier-shaped names (those are join keys, handled below).
263
+ if (version && !/^[a-z][a-z0-9_-]*:.+/i.test(name)) {
264
+ try {
265
+ const qs = new URLSearchParams({ kind: k, key: name, version })
266
+ const vr = await fetchCortex(`${BASE}/api/brain/page?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
267
+ if (vr.status === 404) return { content: [{ type: 'text', text: `No version "${version}" for "${name}" (or you can't see it). Use page_history "${name}" to list its versions.` }] }
268
+ if (!vr.ok) {
269
+ const d = classify(vr.status, vr.headers.get('content-type'), await vr.text(), vr.headers.get('x-vercel-id'))
270
+ return { content: [{ type: 'text', text: `Could not read version "${version}" of "${name}": ${d.message}` }] }
271
+ }
272
+ const v = await vr.json()
273
+ return { content: [{ type: 'text', text: `# ${name} — historical version (rev ${v.revNo} · ${v.op} · ${String(v.createdAt).slice(0, 10)} · ${v.tier})\nversion: ${v.version}\n\n${v.body}\n\n— This is a HISTORICAL snapshot, not the current page. \`read_page "${name}"\` (no version) shows what's live; \`rollback_page\` restores this one as a new version.` }] }
274
+ } catch (e) {
275
+ return { content: [{ type: 'text', text: `Could not read version "${version}" of "${name}": ${e.message}` }] }
276
+ }
277
+ }
260
278
  // PER-NODE TIMELINE (slice 4): history = the projection over the node's identifier stamps.
261
279
  if (history && !/^[a-z][a-z0-9_-]*:.+/i.test(name)) {
262
280
  try {
@@ -353,6 +371,74 @@ export async function runServer(version) {
353
371
  },
354
372
  )
355
373
 
374
+ server.registerTool(
375
+ 'page_history',
376
+ {
377
+ title: 'See a wiki page\'s edit history',
378
+ description: 'Show the VERSION history of an authored wiki page — every prior version, who changed it and when, newest first. This is how you see "what changed on this page and by whom", and it includes privacy changes (re-tiers). Then use `read_page` with a version to view an old body, or `rollback_page` to restore one. (Distinct from read_page\'s `history: true`, which is the raw event timeline via [[repo:…]] stamps.) RLS-scoped: you see history only for pages you may read.',
379
+ inputSchema: {
380
+ name: z.string().describe('the canonical node name exactly as written (e.g. "Cortex", "Ben")'),
381
+ kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('node kind (default project)'),
382
+ limit: z.number().int().optional().describe('how many recent versions to show (default 20, max 200)'),
383
+ },
384
+ },
385
+ async ({ name, kind, limit }) => {
386
+ const k = kind ?? 'project'
387
+ let res
388
+ try {
389
+ const qs = new URLSearchParams({ kind: k, key: name, ...(limit ? { limit: String(limit) } : {}) })
390
+ res = await fetchCortex(`${BASE}/api/brain/page-history?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
391
+ } catch (e) {
392
+ return { content: [{ type: 'text', text: `Could not read history for "${name}": ${e.message}` }] }
393
+ }
394
+ if (res.status === 404) return { content: [{ type: 'text', text: `No ${k} named "${name}". Pass kind (person/org) if it isn't a project.` }] }
395
+ if (!res.ok) {
396
+ const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
397
+ return { content: [{ type: 'text', text: `Could not read history for "${name}": ${d.message}` }] }
398
+ }
399
+ const out = await res.json()
400
+ const revs = out.revisions ?? []
401
+ if (!revs.length) return { content: [{ type: 'text', text: `"${name}" (${k}) has no recorded version history yet.` }] }
402
+ const lines = revs.map((r) => {
403
+ const who = r.actor_name ? ` · ${r.actor_name}` : ''
404
+ const why = r.reason ? ` — ${r.reason}` : ''
405
+ return `- rev ${r.rev_no} · ${String(r.created_at).slice(0, 10)} · ${r.op} · ${r.tier}${who}${why}\n version: ${r.content_hash}`
406
+ })
407
+ return { content: [{ type: 'text', text: `# ${name} — page history (newest first)\n${lines.join('\n')}\n\n— \`read_page "${name}"\` with version:<rev_no|version> to view an old body; \`rollback_page\` to restore one.` }] }
408
+ },
409
+ )
410
+
411
+ server.registerTool(
412
+ 'rollback_page',
413
+ {
414
+ title: 'Roll a wiki page back to a prior version',
415
+ description: 'Restore a wiki page to an earlier version from its page_history — a forward, non-destructive write (the old versions are kept; a new "rollback" version is recorded). Use this to undo a mistaken or bad edit. You may only roll back a page you are allowed to edit. Find the target version with page_history first.',
416
+ inputSchema: {
417
+ name: z.string().describe('the exact page name'),
418
+ kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('node kind (default project)'),
419
+ to_version: z.string().describe('which version to restore: a rev_no (e.g. "3") or a content_hash, from page_history'),
420
+ tier: z.enum(['accessible', 'scoped', 'confidential']).optional().describe('which tier variant to roll back, if the node has more than one'),
421
+ reason: z.string().optional().describe('optional note recorded on the new rollback version (why you rolled back)'),
422
+ },
423
+ },
424
+ async ({ name, kind, to_version, tier, reason }) => {
425
+ const k = kind ?? 'project'
426
+ let res
427
+ try {
428
+ res = await fetchCortex(`${BASE}/api/brain/rollback`, {
429
+ method: 'POST',
430
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
431
+ body: JSON.stringify({ kind: k, name, to_version, ...(tier ? { tier } : {}), ...(reason ? { reason } : {}) }),
432
+ })
433
+ } catch (e) {
434
+ return { content: [{ type: 'text', text: `Could not roll back "${name}": ${e.message}` }] }
435
+ }
436
+ const out = await res.json().catch(() => null)
437
+ if (!res.ok) return { content: [{ type: 'text', text: `Could not roll back "${name}": ${out?.error ?? res.status}` }] }
438
+ return { content: [{ type: 'text', text: `Done — "${name}" ${out.note}. \`read_page "${name}"\` to confirm the current content.` }] }
439
+ },
440
+ )
441
+
356
442
  server.registerTool(
357
443
  'writing_style',
358
444
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.36",
3
+ "version": "0.9.37",
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": {