@theronap/cortex-mcp 0.9.25 → 0.9.27

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 +37 -4
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -249,12 +249,44 @@ export async function runServer(version) {
249
249
  description:
250
250
  'READ the full authored wiki page for one node (project/person/org/you) by its canonical name — every section, every tier you can see. This is how you READ a node; `grep` only LOCATES pages (snippets + their [[links]]), it does not read them. Navigate like a researcher: read the page you need, then FOLLOW its inline [[links]] by calling read_page on each linked name — keep following while the linked pages stay relevant, stop when they do not. You decide how deep to go. Returns only what you are permitted to see.',
251
251
  inputSchema: {
252
- name: z.string().describe('the canonical node name exactly as written (e.g. "Cortex", "Ben", or a [[link]] target)'),
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
+ expand: z.boolean().optional().describe('identifier names only: also list recent visible timeline events for this identifier (default: home + count)'),
254
255
  },
255
256
  },
256
- async ({ name, kind }) => {
257
+ async ({ name, kind, expand }) => {
257
258
  const k = kind ?? 'project'
259
+ // IDENTIFIER RESOLUTION (slice 3): an identifier-shaped name is a JOIN KEY, not a page — resolve
260
+ // it to its authored home + a viewer-honest event count instead of 404ing. Loose shape-detect here;
261
+ // the server enforces the strict canonical law (identifiers.ts) and 400s malformed forms with an
262
+ // actionable message we surface verbatim.
263
+ if (/^[a-z][a-z0-9_-]*:.+/i.test(name)) {
264
+ try {
265
+ const qs = new URLSearchParams({ id: name, ...(expand ? { expand: '1' } : {}) })
266
+ const r = await fetchCortex(`${BASE}/api/identifier/resolve?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
267
+ if (r.ok) {
268
+ const out = await r.json()
269
+ const lines = [`# ${out.identifier} (identifier — a join key, not a page)`]
270
+ if (out.status === 'unhomed') {
271
+ lines.push(`No authored home carries this stamp yet — data but no home. If you know what this identifies, author its page and stamp [[${out.identifier}]] on it (that page becomes the home).`)
272
+ } else if (out.status === 'healthy') {
273
+ const h = out.homes[0]
274
+ lines.push(`Home: "${h.title}" (${h.kind}, ${h.tier}) — \`read_page "${h.title}"\` for the full page.`)
275
+ } else {
276
+ lines.push(`⚠ ${out.homes.length} pages carry this stamp — possible duplicate homes, consider reconciling:`)
277
+ for (const h of out.homes) lines.push(` - "${h.title}" (${h.kind}, ${h.tier})`)
278
+ }
279
+ lines.push(`· ${out.events.visibleCount} tagged timeline event${out.events.visibleCount === 1 ? '' : 's'} visible to you${out.events.recent.length ? ':' : expand ? '.' : ' — pass expand: true to list recent ones.'}`)
280
+ for (const e of out.events.recent) lines.push(` - ${String(e.occurredAt).slice(0, 10)} · ${e.source} · ${e.summary}`)
281
+ return { content: [{ type: 'text', text: lines.join('\n') }] }
282
+ }
283
+ if (r.status === 400) {
284
+ const body = await r.json().catch(() => null)
285
+ return { content: [{ type: 'text', text: `"${name}" looks like an identifier but is not canonical: ${body?.error ?? 'expected repo:owner/name'}. Fix the stamp, or \`grep "${name}"\` to find where it appears.` }] }
286
+ }
287
+ // resolver endpoint unavailable (older server) → fall through to the normal page path below
288
+ } catch { /* network hiccup → fall through to the page path */ }
289
+ }
258
290
  let res
259
291
  try {
260
292
  res = await fetchCortex(`${BASE}/api/brain/page?kind=${k}&key=${encodeURIComponent(name)}`,
@@ -583,7 +615,7 @@ export async function runServer(version) {
583
615
  {
584
616
  title: 'Authoring context (call before author)',
585
617
  description:
586
- 'Fetch the scaffolding to author a Cortex wiki node: the canonical NAMESPACE (existing node names — link to these with the EXACT name inside [[ ]]) and the node-type CONNECTION RULES (what kinds of links to look for). ALWAYS call this BEFORE `author` so the page links to real nodes by their established names instead of minting synonyms. Reference a node in the namespace as [[Name]]; if you reference something real that is NOT in the namespace, still write [[Name]] — that is a red-link marking a node worth creating.',
618
+ 'Fetch the scaffolding to author a Cortex wiki node: the canonical NAMESPACE (existing node names — link to these with the EXACT name inside [[ ]]) and the node-type CONNECTION RULES (what kinds of links to look for). ALWAYS call this BEFORE `author` so the page links to real nodes by their established names instead of minting synonyms. Reference a node in the namespace as [[Name]]; if you reference something real that is NOT in the namespace, still write [[Name]] — that is a red-link marking a node worth creating. If the page IS about a specific code repo, also stamp [[repo:owner/name]] (lowercase) once — an identifier join key, not a page link.',
587
619
  inputSchema: {
588
620
  kind: z.enum(['project', 'person', 'org', 'user']).optional().describe('the node type you are about to author (default project)'),
589
621
  },
@@ -650,8 +682,9 @@ export async function runServer(version) {
650
682
  const blue = out?.links?.blue ?? 0
651
683
  const red = out?.links?.red ?? 0
652
684
  const redList = Array.isArray(out?.redLinks) && out.redLinks.length ? `\nRed-links (wanted nodes): ${out.redLinks.map((r) => `[[${r}]]`).join(', ')}` : ''
685
+ const stamps = Array.isArray(out?.identifiers) && out.identifiers.length ? `\nIdentifier stamps (join keys): ${out.identifiers.map((i) => `[[${i}]]`).join(', ')}` : ''
653
686
  const verb = out?.created ? 'Created + authored' : 'Authored'
654
- const note = out?.built ? `${verb} "${name}" (${out.built} tier${out.built === 1 ? '' : 's'}). Links: ${blue} resolved, ${red} red.${redList}`
687
+ const note = out?.built ? `${verb} "${name}" (${out.built} tier${out.built === 1 ? '' : 's'}). Links: ${blue} resolved, ${red} red.${redList}${stamps}`
655
688
  : `No change to "${name}"${out?.skipped?.length ? ` (${out.skipped.join(', ')})` : ''}.`
656
689
  return { content: [{ type: 'text', text: note }] }
657
690
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.25",
3
+ "version": "0.9.27",
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": {