@theronap/agnoclast-mcp 0.9.240 → 0.9.242

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.
@@ -30,7 +30,7 @@ export function mergeClaudeMcp(existing, spec, token) {
30
30
  export const ALLOWED_TOOL_NAMES = [
31
31
  // read surface
32
32
  'grep', 'read_page', 'my_context', 'project_status', 'session_context', 'search_org',
33
- 'list_records', 'read_record', 'page_history', 'page_diff', 'timeline_pull', 'my_brains', 'my_sessions', 'writing_style',
33
+ 'list_records', 'read_record', 'page_history', 'page_diff', 'timeline_pull', 'my_brains', 'my_sessions', 'token_activity', 'writing_style',
34
34
  'code_graph_query', 'my_retier_notices', 'list_page_grants', 'list_project_members', 'file_requests',
35
35
  // the questions agents used to answer with SQL (2026-10-07) — read-only, the caller's own view
36
36
  'record_status', 'my_connectors', 'brain_stats', 'my_sends', 'find_arrivals',
package/lib/server.mjs CHANGED
@@ -2364,7 +2364,10 @@ function renderNudge(payload) {
2364
2364
  const revs = out.revisions ?? []
2365
2365
  if (!revs.length) return { content: [{ type: 'text', text: `"${name}" (${k}) has no recorded version history yet.` }] }
2366
2366
  const lines = revs.map((r) => {
2367
- const who = r.actor_name ? ` · ${r.actor_name}` : ''
2367
+ // "Theron via Frack" — the label only ever reaches the token's owner (bdb2339b phase 2).
2368
+ const via = r.via_token_label ? ` via ${r.via_token_label}` : ''
2369
+ const agentOf = r.actor_agent_of ? ` (agent of ${r.actor_agent_of})` : ''
2370
+ const who = r.actor_name ? ` · ${r.actor_name}${agentOf}${via}` : (via ? ` ·${via}` : '')
2368
2371
  // change_kind first and bracketed so a column of [correct] is scannable — the whole point is
2369
2372
  // that a page with repeated corrections looks different at a glance from one that only grew.
2370
2373
  const what = r.change_kind ? ` · [${r.change_kind}]` : ''
@@ -2419,7 +2422,7 @@ function renderNudge(payload) {
2419
2422
  ? `# ${name} — rev ${d.to.revNo} (first version)`
2420
2423
  : `# ${name} — rev ${d.from.revNo} → rev ${d.to.revNo}`
2421
2424
  const meta = []
2422
- meta.push(`${String(d.to.createdAt).slice(0, 10)} · ${d.to.op}${d.to.changeKind ? ` · [${d.to.changeKind}]` : ''}${d.to.actorName ? ` · ${d.to.actorName}` : ''}`)
2425
+ meta.push(`${String(d.to.createdAt).slice(0, 10)} · ${d.to.op}${d.to.changeKind ? ` · [${d.to.changeKind}]` : ''}${d.to.actorName ? ` · ${d.to.actorName}${d.to.actorAgentOf ? ` (agent of ${d.to.actorAgentOf})` : ''}${d.to.viaTokenLabel ? ` via ${d.to.viaTokenLabel}` : ''}` : ''}`)
2423
2426
  if (d.to.reason) meta.push(`reason: ${d.to.reason}`)
2424
2427
  if (d.to.sessionKey) meta.push(`session: ${String(d.to.sessionKey).slice(0, 24)}`)
2425
2428
  const body = []
@@ -4916,6 +4919,50 @@ function renderNudge(payload) {
4916
4919
  },
4917
4920
  )
4918
4921
 
4922
+ // share_calendar — share YOUR OWN calendar with one person (ADR-0086; Theron, 2026-10-17). Same shape as
4923
+ // share_record: NOT auto-allowed (a disclosure to someone, so it prompts), and only when the person asks.
4924
+ // Nothing is emailed by this tool; it returns the link for the person to send.
4925
+ server.registerTool(
4926
+ 'share_calendar',
4927
+ {
4928
+ title: 'Share your calendar with someone',
4929
+ description:
4930
+ 'Share the person\'s OWN calendar with one other person by email address. They get their own read-only link ' +
4931
+ '(agnoclast.com/c/…); if they use Agnoclast it also appears on their calendar until revoked. scope "busy" ' +
4932
+ '(default) shows only busy blocks; "titles" also shows event titles (never notes, places or attendees). This does ' +
4933
+ 'NOT send anything: give the person the link to send, or they can send it from the Share calendar panel. Only ' +
4934
+ 'when the person asks to share their calendar; never on your own initiative. Turn it off on agnoclast.com/calendar.',
4935
+ inputSchema: {
4936
+ email: z.string().describe('the email address of the person to share with'),
4937
+ scope: z.enum(['busy', 'titles']).optional().describe('busy = busy blocks only (default); titles = event titles too'),
4938
+ },
4939
+ },
4940
+ async ({ email, scope }) => {
4941
+ let res
4942
+ try {
4943
+ res = await fetchCortex(`${BASE}/api/calendar/shares`, {
4944
+ method: 'POST',
4945
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
4946
+ body: JSON.stringify({ action: 'share_with', email, scope: scope ?? 'busy' }),
4947
+ })
4948
+ } catch (e) {
4949
+ return toolError(`Could not share the calendar: ${e.message}`)
4950
+ }
4951
+ if (res.status === 400 || res.status === 409) {
4952
+ const b = await res.json().catch(() => null)
4953
+ return toolError(`Not shared: ${b?.error ?? 'refused'}`)
4954
+ }
4955
+ if (!res.ok) {
4956
+ const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
4957
+ return toolError(`Could not share the calendar: ${d.message}`)
4958
+ }
4959
+ const out = await res.json()
4960
+ return { content: [{ type: 'text', text:
4961
+ `Shared your calendar (${out.share.scope === 'titles' ? 'with event titles' : 'busy times only'}) with ${out.share.recipientEmail}:\n${out.url}\n` +
4962
+ `Nothing was sent — give them this link. It is shown only once. If they use Agnoclast it is already on their calendar. share id: ${out.share.id}` }] }
4963
+ },
4964
+ )
4965
+
4919
4966
  // set_record_notes — write the NOTES layer of one of your own transcripts (Theron, 2026-10-08: "two
4920
4967
  // layers … a summary and notes from the transcript … the full transcript"). The server keeps the
4921
4968
  // before-state in record history and refuses anything that is not your own kept transcript.
@@ -5370,6 +5417,46 @@ function renderNudge(payload) {
5370
5417
  },
5371
5418
  )
5372
5419
 
5420
+ server.registerTool(
5421
+ 'token_activity',
5422
+ {
5423
+ title: 'What this agent token has done',
5424
+ description: "Audit YOUR OWN recent activity through the token you are connected with: tool calls, wiki page edits, record placements and intake discards, newest first. Use it to report back what you did (\"what did I discard today?\"), or to check a run before handing off. Self-only: it reads the CALLING token's activity and never another agent's — the person audits all their tokens from Connect → Agent tokens. Discards are content-free by design, so they name the source, not the item. Errors if you are not connected with an agent token (console sessions have none).",
5425
+ inputSchema: {
5426
+ days: z.number().int().optional().describe('how far back (default 7, max 30)'),
5427
+ kinds: z.array(z.enum(['call', 'edit', 'placement', 'discard'])).optional().describe('only these kinds (default all)'),
5428
+ limit: z.number().int().optional().describe('max items (default 100, max 500)'),
5429
+ },
5430
+ },
5431
+ async ({ days, kinds, limit }) => {
5432
+ let res
5433
+ try {
5434
+ const qs = new URLSearchParams({
5435
+ ...(days ? { days: String(days) } : {}), ...(limit ? { limit: String(limit) } : {}),
5436
+ ...(kinds?.length ? { kinds: kinds.join(',') } : {}),
5437
+ })
5438
+ res = await fetchCortex(`${BASE}/api/me/token-activity?${qs}`, { headers: { Authorization: `Bearer ${TOKEN}` } })
5439
+ } catch (e) {
5440
+ return toolError(`Could not read this token's activity: ${e.message}`)
5441
+ }
5442
+ if (res.status === 400) return { content: [{ type: 'text', text: 'You are not connected with an agent token, so there is no per-token activity to show. The person can see every token\'s activity under Connect → Agent tokens.' }] }
5443
+ if (!res.ok) {
5444
+ const d = classify(res.status, res.headers.get('content-type'), await res.text(), res.headers.get('x-vercel-id'))
5445
+ return toolError(`Could not read this token's activity: ${d.message}`)
5446
+ }
5447
+ const out = await res.json()
5448
+ const name = out.token?.label ?? 'this token'
5449
+ const items = out.items ?? []
5450
+ if (!items.length) return { content: [{ type: 'text', text: `${name}: no activity in the last ${days ?? 7} day(s).` }] }
5451
+ const counts = {}
5452
+ for (const i of items) counts[i.kind] = (counts[i.kind] ?? 0) + 1
5453
+ const lines = items.map((i) =>
5454
+ `- ${String(i.at).slice(0, 16).replace('T', ' ')} · ${i.kind} · ${i.what ?? '—'}${i.detail ? ` · ${i.detail}` : ''}${i.reason ? ` — ${i.reason}` : ''}`)
5455
+ const summary = Object.entries(counts).map(([k, n]) => `${n} ${k}`).join(' · ')
5456
+ return { content: [{ type: 'text', text: `# ${name} — activity, last ${days ?? 7} day(s), newest first (UTC)\n${summary}\n\n${lines.join('\n')}` }] }
5457
+ },
5458
+ )
5459
+
5373
5460
  server.registerTool(
5374
5461
  'my_sessions',
5375
5462
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.240",
3
+ "version": "0.9.242",
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": {