@theronap/cortex-mcp 0.9.124 → 0.9.125

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 +58 -1
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -1883,13 +1883,70 @@ function renderNudge(payload) {
1883
1883
  },
1884
1884
  )
1885
1885
 
1886
+ server.registerTool(
1887
+ 'staged_records',
1888
+ {
1889
+ title: 'List staged arrivals — the ones in no brain yet, with their ids',
1890
+ description: "List arrivals that are STAGED: held in no brain at all, because nothing could decide which brain they belong to, or because they carry identifiers the graph has never seen. This is the ONLY way to obtain a `staged_id`. `pending_records` structurally cannot see these (it reads records, and a staged row is not one); `intake_changes` returns ids from a different queue that `place_staged_record` rejects; and the session-start block prints titles without ids. Until this existed, placing a staged arrival required a human to read a uuid off a screen. Returns each arrival's id, title, age and the identifiers BLOCKING it, plus `blocking_summary` — the unclaimed identifiers ranked by how many arrivals each one holds, so you can see which single `set_routing_identifier` releases the most. ⚠ Claiming an identifier UNBLOCKS but does not place: which pages an arrival lands on decides its brain, and therefore who can read it, so that stays a decision someone makes.",
1891
+ inputSchema: {
1892
+ limit: z.number().optional().describe('max arrivals to return (default 50, max 200). `total` always reports the WHOLE queue regardless.'),
1893
+ source: z.string().optional().describe("narrow to one connector kind, e.g. 'email' or 'github'"),
1894
+ },
1895
+ },
1896
+ async ({ limit, source }) => {
1897
+ const qs = new URLSearchParams()
1898
+ if (limit) qs.set('limit', String(limit))
1899
+ if (source) qs.set('source', source)
1900
+ let res
1901
+ try {
1902
+ res = await fetchCortex(`${BASE}/api/staged${qs.toString() ? `?${qs}` : ''}`, {
1903
+ headers: { Authorization: `Bearer ${TOKEN}` },
1904
+ })
1905
+ } catch (e) {
1906
+ return toolError(`Could not list staged arrivals: ${e.message}`)
1907
+ }
1908
+ const out = await res.json().catch(() => null)
1909
+ if (!res.ok) return toolError(`Could not list staged arrivals: ${out?.error ?? res.status}`)
1910
+ if (!out) return toolError('The server returned no body.')
1911
+
1912
+ if (!out.total) {
1913
+ return { content: [{ type: 'text', text: 'Nothing staged — every arrival has reached a brain.' }] }
1914
+ }
1915
+ const lines = [`${out.total} staged arrival(s) — in NO brain yet:`, '']
1916
+ for (const a of out.arrivals ?? []) {
1917
+ // The id goes FIRST on its own line. It is the argument place_staged_record needs and the
1918
+ // entire reason this tool exists; burying it after prose is how the session-start block
1919
+ // managed to list these rows for weeks without making one of them actionable.
1920
+ lines.push(` ${a.stagedId}`)
1921
+ lines.push(` ${a.title ?? '(no subject)'} · ${a.sourceType} · ${a.occurredAt?.slice(0, 10) ?? ''}`)
1922
+ if (a.blockingIdentifiers?.length) {
1923
+ lines.push(` blocked by: ${a.blockingIdentifiers.join(', ')}`)
1924
+ } else {
1925
+ lines.push(' held because no brain could be determined (no unknown identifiers)')
1926
+ }
1927
+ }
1928
+ if ((out.arrivals ?? []).length < out.total) {
1929
+ lines.push('', ` … ${out.total - out.arrivals.length} more not shown — raise \`limit\` to see them.`)
1930
+ }
1931
+ if (out.blockingSummary?.length) {
1932
+ lines.push('', 'Unclaimed identifiers holding the most arrivals — claim one and that many unblock:')
1933
+ for (const b of out.blockingSummary.slice(0, 10)) {
1934
+ lines.push(` ${b.arrivals}x ${b.identifier}`)
1935
+ }
1936
+ lines.push('', 'Claim with set_routing_identifier on the page whose subject HAS that identifier.')
1937
+ }
1938
+ lines.push('', 'Place one with place_staged_record (staged_id + pages). The pages decide the brain, which decides who can read it.')
1939
+ return { content: [{ type: 'text', text: lines.join('\n') }] }
1940
+ },
1941
+ )
1942
+
1886
1943
  server.registerTool(
1887
1944
  'place_staged_record',
1888
1945
  {
1889
1946
  title: 'Place a staged arrival onto pages — the pages decide its brain',
1890
1947
  description: "Place a STAGED arrival — one that is in no brain at all — onto the pages it belongs to, which is also what decides its brain. Session-start lists these separately as `[staged]`, and they are the only rows route_record CANNOT take, because there is no record yet to route: nothing upstream chose a brain for them, deliberately. That is the point (ADR-0038) — the connector used to pick the brain from which mailbox the message arrived through, which is a fact about your email plumbing rather than about the message, and it decided WHO COULD READ IT before anyone had read it. Here the pages decide instead. ⚠ PLACING IS A DISCLOSURE DECISION, not just filing: a brain is the confidentiality boundary, so putting a staged message into a shared brain makes it readable by every member of that brain. Say so when you offer, and never place a personal message into a shared brain without the owner\'s explicit answer. All the pages must live in ONE brain — a record exists in exactly one — and pages spanning two brains are refused by name rather than resolved by picking. On success the content is replayed through the real ingest pipeline into that brain, so the record it produces is identical to one that had landed there directly, and then it is attached.",
1891
1948
  inputSchema: {
1892
- staged_id: z.string().describe('the staged id, as the [staged] rows at session start show it'),
1949
+ staged_id: z.string().describe('the staged id — get it from `staged_records`, which is the only surface that prints one (session-start lists these arrivals but not their ids)'),
1893
1950
  pages: z.array(z.string()).describe("pages to place it on — a brain_documents id or the `ref:` read_page prints. They must all be in ONE brain; that brain is where the record lands."),
1894
1951
  reason: z.string().describe('WHY these pages — recorded with the attachment, and the one thing that cannot be inferred later'),
1895
1952
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/cortex-mcp",
3
- "version": "0.9.124",
3
+ "version": "0.9.125",
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": {