@theronap/agnoclast-mcp 0.9.189 → 0.9.190

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.
@@ -36,11 +36,13 @@ export const ALLOWED_TOOL_NAMES = [
36
36
  'authoring_context', 'author', 'log_session',
37
37
  // routine, reversible maintenance
38
38
  'set_page_validity', 'snooze_red_link', 'attribute_thread',
39
+ // clears only the caller's OWN notice that a page was handed to them; the page is already theirs
40
+ 'confirm_page_ownership',
39
41
  ]
40
42
 
41
43
  // ⚠ DELIBERATELY ABSENT, and not an oversight to be tidied up later: `grant_page_access`,
42
- // `transfer_page_ownership` and `set_project_membership`. Each one changes WHO CAN SEE SOMEONE ELSE'S
43
- // WORK, so each one prompts, every time, even on a seat that has pre-approved everything else. The
44
+ // `transfer_page_ownership`, `set_project_membership` and `set_successor` (which names who would
45
+ // read your pages if you left). Each one changes WHO CAN SEE SOMEONE ELSE'S WORK, so each one prompts, every time, even on a seat that has pre-approved everything else. The
44
46
  // listing halves (`list_page_grants`, `list_project_members`) are on the allowlist above because
45
47
  // reading who already has access discloses nothing the viewer could not already ask a person, and
46
48
  // making the check expensive is how it stops being done before the grant.
package/lib/server.mjs CHANGED
@@ -5239,7 +5239,7 @@ function renderNudge(payload) {
5239
5239
  'transfer_page_ownership',
5240
5240
  {
5241
5241
  title: 'Hand a page over to someone else',
5242
- description: 'Make another org member the owner of a wiki page — "put Dana in charge of the Northwind page". Ownership decides who may re-tier it, who may set its edit policy, and (for scoped/confidential pages) who can read it, so this is a real handover, not a label. Allowed for the page\'s owner, or for a manager-ancestor who can already read it; pass yourself as `to` to TAKE ownership, which is the documented path for a manager who then needs to change policy. Recorded in page history with you as the actor. The page keeps its tier — transferring does not re-tier, so a scoped page handed to someone outside the new owner\'s chain can become unreadable to its previous audience; re-tier separately if that matters.',
5242
+ description: 'Make another org member the owner of a wiki page — "put Dana in charge of the Northwind page". Ownership decides who may re-tier it, who may set its edit policy, and (for scoped/confidential pages) who can read it, so this is a real handover, not a label. Allowed for the page\'s owner, or for a manager-ancestor who can already read it; pass yourself as `to` to TAKE ownership, which is the documented path for a manager who then needs to change policy. When the owner has LEFT the brain, their page is handed on by its steward — their manager, the most senior person they shared it with, or their named successor — who sees it listed under "Pages waiting on you as steward" at session start and may transfer it even without read access (taking it yourself is how you read it). Someone else receiving a page is told at their next session start. Recorded in page history with you as the actor. The page keeps its tier — transferring does not re-tier, so a scoped page handed to someone outside the new owner\'s chain can become unreadable to its previous audience; re-tier separately if that matters.',
5243
5243
  inputSchema: {
5244
5244
  kind: z.enum(['project', 'person', 'org', 'user']).describe('the page kind'),
5245
5245
  name: z.string().optional().describe('the exact page name (or pass `ref` instead — one of the two is required)'),
@@ -5277,6 +5277,99 @@ function renderNudge(payload) {
5277
5277
  },
5278
5278
  )
5279
5279
 
5280
+ // PAGES HANDED TO YOU — the "and confirms" half of the stewardship removal design (Theron, 2026-09-18;
5281
+ // built 2026-09-30). transferPageOwnership writes a notice whenever someone else hands you a page, and
5282
+ // the session-start block lists it until you confirm here. No refusal verb, on purpose: Theron ruled
5283
+ // one out, because the new owner already holds transfer_page_ownership.
5284
+ server.registerTool(
5285
+ 'confirm_page_ownership',
5286
+ {
5287
+ title: 'Confirm pages handed to you',
5288
+ description: 'When someone hands you a page — a colleague using transfer_page_ownership, or a steward rehoming a departed member\'s page — your session start lists it under "Pages handed to you" until you confirm it. Pass the page `refs` (as listed there) to confirm those, or `all: true` for every open one. Confirming only clears the notice: the page is already yours. To pass one on instead, use transfer_page_ownership.',
5289
+ inputSchema: {
5290
+ refs: z.array(z.string()).optional().describe('page refs, as listed under "Pages handed to you"'),
5291
+ all: z.boolean().optional().describe('true = confirm every open notice'),
5292
+ },
5293
+ },
5294
+ async ({ refs, all }) => {
5295
+ if (!all && !(refs && refs.length)) return toolError('Pass `refs` (the pages to confirm) or `all: true`.')
5296
+ let res
5297
+ try {
5298
+ res = await fetchCortex(`${BASE}/api/brain/ownership-notices`, {
5299
+ method: 'POST',
5300
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
5301
+ body: JSON.stringify(all ? { all: true } : { refs }),
5302
+ })
5303
+ } catch (e) {
5304
+ return toolError(`Could not confirm: ${e.message}`)
5305
+ }
5306
+ const out = await res.json().catch(() => null)
5307
+ if (!res.ok) {
5308
+ if (out?.error) return toolError(`Could not confirm: ${out.error}`)
5309
+ const d = classify(res.status, res.headers.get('content-type'), '', res.headers.get('x-vercel-id'))
5310
+ return toolError(`Could not confirm: ${d.message}`)
5311
+ }
5312
+ const n = Number(out?.confirmed ?? 0)
5313
+ return {
5314
+ content: [{
5315
+ type: 'text',
5316
+ text: n === 0
5317
+ ? 'Nothing to confirm — no open notice matched. It may already be confirmed, or the page has since moved on to someone else.'
5318
+ : `Confirmed ${n} page${n === 1 ? '' : 's'}. ${n === 1 ? 'It stays' : 'They stay'} yours, and the notice will not show again.`,
5319
+ }],
5320
+ }
5321
+ },
5322
+ )
5323
+
5324
+ // SUCCESSOR — who inherits your pages if you leave a brain where you have no manager (20260919071500).
5325
+ // The route existed from 2026-09-19 and nothing called it, so no root could actually name one; the
5326
+ // session-start block now nudges anyone who needs to, and this is where they answer.
5327
+ server.registerTool(
5328
+ 'set_successor',
5329
+ {
5330
+ title: 'Name who inherits your pages if you leave',
5331
+ description: 'If you have no manager in a brain, name a successor there: the person who takes over any page of yours that nobody else can see, should you ever leave. Pass `successor` as their email (an active member of that same brain), or null to clear it; pass `brain` if you are in more than one. Called with no `successor`, it just shows your current successor in each brain and where one is still needed. Self-service only — nobody can set yours for you.',
5332
+ inputSchema: {
5333
+ successor: z.string().nullable().optional().describe('their email or user id; null clears it; omit to only show the current setting'),
5334
+ brain: z.string().optional().describe('brain name or org id — needed only if you are in more than one'),
5335
+ },
5336
+ },
5337
+ async ({ successor, brain }) => {
5338
+ const show = successor === undefined
5339
+ let res
5340
+ try {
5341
+ res = await fetchCortex(`${BASE}/api/me/successor`, show
5342
+ ? { headers: { Authorization: `Bearer ${TOKEN}` } }
5343
+ : {
5344
+ method: 'POST',
5345
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
5346
+ body: JSON.stringify({ successor, ...(brain ? { brain } : {}) }),
5347
+ })
5348
+ } catch (e) {
5349
+ return toolError(`Could not ${show ? 'read' : 'set'} your successor: ${e.message}`)
5350
+ }
5351
+ const out = await res.json().catch(() => null)
5352
+ if (!res.ok) {
5353
+ if (out?.error) return toolError(`Could not ${show ? 'read' : 'set'} your successor: ${out.error}${out.brains ? ` (your brains: ${out.brains.join(', ')})` : ''}`)
5354
+ const d = classify(res.status, res.headers.get('content-type'), '', res.headers.get('x-vercel-id'))
5355
+ return toolError(`Could not ${show ? 'read' : 'set'} your successor: ${d.message}`)
5356
+ }
5357
+ if (show) {
5358
+ const rows = (out?.memberships ?? []).map((m) =>
5359
+ `- ${m.brain}: ${m.successor ? `${m.successor.name} (${m.successor.email})` : 'none'}${m.needed ? ' — ⚠ needed: you have no manager here' : ''}`)
5360
+ return { content: [{ type: 'text', text: rows.length ? rows.join('\n') : 'You have no active memberships.' }] }
5361
+ }
5362
+ return {
5363
+ content: [{
5364
+ type: 'text',
5365
+ text: out?.successor
5366
+ ? `Done — in ${out.brain}, ${out.successor.name} (${out.successor.email}) now inherits any page of yours nobody else can see, if you leave.`
5367
+ : `Done — your successor in ${out?.brain} is cleared.`,
5368
+ }],
5369
+ }
5370
+ },
5371
+ )
5372
+
5280
5373
  server.registerTool(
5281
5374
  'my_retier_notices',
5282
5375
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.189",
3
+ "version": "0.9.190",
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": {