@theronap/agnoclast-mcp 0.9.230 → 0.9.231

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.
@@ -27,3 +27,24 @@ export function renderRemoved(out, person) {
27
27
  for (const w of Array.isArray(out?.warnings) ? out.warnings : []) lines.push(`- ${one(w, 400)}`)
28
28
  return lines.join('\n')
29
29
  }
30
+
31
+ // ── leave_brain (2026-10-12): the same two-call contract, said to the person who is leaving ──
32
+ export function renderLeaveQuestion(out) {
33
+ const p = out?.preview ?? {}
34
+ const lines = [
35
+ `NOT LEFT — this needs the person's yes.${out?.stale ? ' (The picture changed since the last confirmation; this is the current one.)' : ''}`,
36
+ `Leave the brain "${one(p.brain, 80)}"? You lose your role there (${one(p.role, 40)}) and can no longer open anything in it.`,
37
+ 'Your login, your other brains and everything you wrote stay. Nothing is deleted; someone in the brain can invite you back.',
38
+ ]
39
+ for (const s of Array.isArray(p.summary) ? p.summary : []) lines.push(one(s, 400).replace(/^Their /, 'Your '))
40
+ lines.push(`Tell the person exactly this and ask. Only if they say yes: leave_brain again with the same brain and confirm: "${one(out?.confirm_token, 40)}".`)
41
+ return lines.join('\n')
42
+ }
43
+
44
+ export function renderLeft(out) {
45
+ const lines = [out?.alreadyInactive
46
+ ? 'You had already left this brain — nothing more to do.'
47
+ : `You left "${one(out?.orgName, 80)}". You can no longer open anything in it.`]
48
+ for (const w of Array.isArray(out?.warnings) ? out.warnings : []) lines.push(`- ${one(w, 400)}`)
49
+ return lines.join('\n')
50
+ }
package/lib/server.mjs CHANGED
@@ -22,7 +22,7 @@ import { repoFullNameFrom, workingFilesFrom } from './capture.mjs'
22
22
  import { findTranscript, createWorkedFilesTracker } from './working_files.mjs'
23
23
  import { spawnObligationWorker } from './obligations_worker.mjs'
24
24
  import { renderObligations, renderPageObligations, tierReads, who } from './obligations_render.mjs'
25
- import { renderRemovalQuestion, renderRemoved } from './member_remove_render.mjs'
25
+ import { renderLeaveQuestion, renderLeft, renderRemovalQuestion, renderRemoved } from './member_remove_render.mjs'
26
26
  import { renderSettings } from './settings_render.mjs'
27
27
  import { renderConversationOffer, renderMaterializeBatch } from './intake_batch_render.mjs'
28
28
  import { obligationsForUnits, renderFilingObligations, filingObligationsBrief, OBLIGATIONS_UNANSWERED_TEXT } from './filing_obligations.mjs'
@@ -3221,6 +3221,40 @@ function renderNudge(payload) {
3221
3221
  },
3222
3222
  )
3223
3223
 
3224
+ // 2026-10-12. A member leaves a brain THEMSELVES — the remove_member path with the caller as the target.
3225
+ server.registerTool(
3226
+ 'leave_brain',
3227
+ {
3228
+ title: 'Leave a brain',
3229
+ description: "Take the person OUT of one of their own brains — ONLY when the person asked you to leave it. Never suggest or do it on your own. The FIRST call changes nothing: it answers with which brain, the role they would lose, and where every one of their pages would go (to a steward — usually their manager), plus a confirm token. Tell the person exactly that and ask. Only if they say yes, call again with the same `brain` and `confirm` set to that token. Never pass a confirm token the person has not just approved. Leaving is soft and per brain: their login, their other brains and everything they wrote stay; nothing is deleted. The last active owner of a brain cannot leave it — they must make another member an owner first.",
3230
+ inputSchema: {
3231
+ brain: z.string().optional().describe("which brain to leave — its name or org id (from my_brains). Required if the person belongs to more than one brain; if two of their brains share that name, pass the org id"),
3232
+ confirm: z.string().optional().describe('ONLY after the person has seen what leaving does and said yes: the confirm token from the first call'),
3233
+ },
3234
+ },
3235
+ async ({ brain, confirm }) => {
3236
+ let res
3237
+ try {
3238
+ res = await fetchCortex(`${BASE}/api/members/leave`, {
3239
+ method: 'POST',
3240
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
3241
+ body: JSON.stringify({ brain, confirm }),
3242
+ })
3243
+ } catch (e) { return toolError(`Could not reach Agnoclast: ${e.message}`) }
3244
+ const out = await res.json().catch(() => null)
3245
+ if (res.status === 409 && out?.error === 'confirm_leave') {
3246
+ return { content: [{ type: 'text', text: renderLeaveQuestion(out) }] }
3247
+ }
3248
+ if (!res.ok) {
3249
+ const cands = Array.isArray(out?.candidates) && out.candidates.length
3250
+ ? ` Candidates: ${out.candidates.map((c) => `"${c.brain}" (org id ${c.orgId})`).join(', ')}.`
3251
+ : ''
3252
+ return toolError(`Not left: ${out?.detail ?? out?.error ?? res.status}${cands}`)
3253
+ }
3254
+ return { content: [{ type: 'text', text: renderLeft(out) }] }
3255
+ },
3256
+ )
3257
+
3224
3258
  server.registerTool(
3225
3259
  'set_obligation_tier',
3226
3260
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.230",
3
+ "version": "0.9.231",
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": {