@theronap/agnoclast-mcp 0.9.224 → 0.9.226

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 +57 -4
  2. package/package.json +1 -1
package/lib/server.mjs CHANGED
@@ -4685,6 +4685,49 @@ function renderNudge(payload) {
4685
4685
  },
4686
4686
  )
4687
4687
 
4688
+ // relabel_record_people — put a real name where a text record names its person by a bare phone number
4689
+ // (records filed before desktop 0.1.40 could not read Contacts). Owner-only; labels, never identifiers or
4690
+ // message bodies; dry run unless apply: true; the server keeps each before-state in record history.
4691
+ server.registerTool(
4692
+ 'relabel_record_people',
4693
+ {
4694
+ title: 'Put a name on texts that show a bare phone number',
4695
+ description:
4696
+ 'On YOUR OWN text-message records, replace a bare phone number standing as a person\'s name ("1 message with ' +
4697
+ '+18015550100") with the name you give: the summary, the thread label and the "who:" speaker prefix of each ' +
4698
+ 'message. Never changes the number itself as an identifier, message wording, or who can see the record. ' +
4699
+ 'Dry run by default (reports how many records would change); pass apply: true to write. Safe to repeat.',
4700
+ inputSchema: {
4701
+ mappings: z.array(z.object({
4702
+ phone: z.string().describe('E.164, e.g. +18015550100 (a "phone:" prefix is fine)'),
4703
+ name: z.string().describe('the display name to show instead'),
4704
+ })).min(1).max(50).describe('one or more number → name pairs'),
4705
+ apply: z.boolean().optional().describe('true = write; omitted/false = dry run'),
4706
+ },
4707
+ },
4708
+ async ({ mappings, apply }) => {
4709
+ let res
4710
+ try {
4711
+ res = await fetchCortex(`${BASE}/api/records/relabel-people`, {
4712
+ method: 'POST',
4713
+ headers: { Authorization: `Bearer ${TOKEN}`, 'Content-Type': 'application/json' },
4714
+ body: JSON.stringify({ mappings, dry_run: apply !== true }),
4715
+ })
4716
+ } catch (e) {
4717
+ return toolError(`Could not reach Agnoclast: ${e.message}`)
4718
+ }
4719
+ const body = await res.json().catch(() => ({}))
4720
+ if (res.status === 404 && !body?.error) return toolError('This server does not relabel records yet. Nothing was written.')
4721
+ if (res.status === 422) return toolError(`Not done: ${body?.detail ?? 'the mappings were refused'}.`)
4722
+ if (!res.ok || !body?.ok) return toolError(`Could not relabel: HTTP ${res.status} ${JSON.stringify(body)}`)
4723
+ const ids = (body.recordIds ?? []).join(', ')
4724
+ const text = body.dryRun
4725
+ ? `Dry run: ${body.changed} of your text records would be relabelled (${body.matched} mention the number${body.matched === 1 ? '' : 's'}).${ids ? ` Records: ${ids}.` : ''} Nothing was written; pass apply: true to write.`
4726
+ : `Relabelled ${body.changed} of your text records.${ids ? ` Records: ${ids}.` : ''} Each before-state is kept in record history.`
4727
+ return { content: [{ type: 'text', text }] }
4728
+ },
4729
+ )
4730
+
4688
4731
  // ── the questions agents used to answer with SQL (Agnoclast page "Agent SQL gap", 2026-10-07) ──────
4689
4732
  // Theron, 2026-10-07: "any information that I have felt the need to get about my own brains, but was
4690
4733
  // not able to do it with Agnoclast tools … we either need to make tools for that, or …". Measured the
@@ -5270,13 +5313,14 @@ function renderNudge(payload) {
5270
5313
  'set_routing_identifier',
5271
5314
  {
5272
5315
  title: 'Claim a routing identifier on a page',
5273
- description: 'Declare that a page is a Gate 4 attach HOME for an identifier (repo:owner/name or file:owner/name:path). Body [[repo:…]] stamps are navigation only and do NOT drive attach — use this instead. Prefer the narrowest id: mother pages own repo:; feature pages own file: paths. Do not copy every body mention into a routing claim.',
5316
+ description: 'Declare that a page is a Gate 4 attach HOME for an identifier (repo:owner/name or file:owner/name:path). Body [[repo:…]] stamps are navigation only and do NOT drive attach — use this instead. Prefer the narrowest id: mother pages own repo:; feature pages own file: paths. Do not copy every body mention into a routing claim. ' +
5317
+ 'PRIVATE CONTACT CLAIMS (ADR-0079): on a PERSON page you may claim `phone:<number>` (normalized to E.164, US default — `phone:(801) 636-7505` stores `phone:+18016367505`) or `email:<address>` (lowercased). These are visible ONLY to you — never in the page body, grep, or to other brain members — and they make your captured texts / mail from that contact propose that person page. This is the right place for a contact\'s number; never put it in page text.',
5274
5318
  inputSchema: {
5275
5319
  kind: z.enum(['project', 'person', 'org', 'user']).describe('the page kind'),
5276
5320
  name: z.string().optional().describe('page title (or pass ref)'),
5277
5321
  ref: z.string().optional().describe('node ref from read_page — prefer over name when available'),
5278
5322
  brain: z.string().optional().describe('brain label when the title is ambiguous across brains'),
5279
- identifier: z.string().describe('canonical identifier, e.g. repo:theronap/cortex or file:theronap/cortex:web/lib/engine/github_intake.ts'),
5323
+ identifier: z.string().describe('canonical identifier, e.g. repo:theronap/cortex, file:theronap/cortex:web/lib/engine/github_intake.ts, or on a person page phone:+18015550123 / email:someone@example.com (private to you)'),
5280
5324
  },
5281
5325
  },
5282
5326
  async ({ kind, name, ref, brain, identifier }) => {
@@ -5292,7 +5336,12 @@ function renderNudge(payload) {
5292
5336
  return toolError(`Could not set routing identifier: ${e.message}`)
5293
5337
  }
5294
5338
  const out = await res.json().catch(() => null)
5295
- if (!res.ok) return toolError(`Could not set routing identifier: ${out?.error ?? res.status}`)
5339
+ if (!res.ok) {
5340
+ // A per-identifier refusal arrives in `errors[]` with a top-level 400 — name it, not just the status.
5341
+ const why = out?.error ?? out?.errors?.map((e) => e.error).join(', ') ?? res.status
5342
+ const hint = /phone_claims_person_pages_only/.test(String(why)) ? ' A phone: claim goes on the PERSON page for that contact (kind: person).' : ''
5343
+ return toolError(`Could not set routing identifier: ${why}.${hint}`)
5344
+ }
5296
5345
  const set = out?.set?.join(', ') ?? identifier
5297
5346
  // ⚠ SAY WHAT ALREADY HAPPENED, NOT ONLY WHAT WILL. The claim heals history in the same
5298
5347
  // statement, so by the time this renders, existing records are ALREADY attached. The previous
@@ -5306,7 +5355,11 @@ function renderNudge(payload) {
5306
5355
  : healed > 0
5307
5356
  ? ` ${healed} existing record(s) ALREADY attached to this page as a result.`
5308
5357
  : ' 0 existing records attached — either nothing carries this identifier yet, or records do but belong to another owner (an identity claim only attaches your own).'
5309
- return { content: [{ type: 'text', text: `Routing identifier set on document ${out?.documentId ?? '?'}: ${set}.${healedLine} Future matching events will attach here too (body mentions alone will not).` }] }
5358
+ const isContact = /^(phone|email):/.test(String(out?.set?.[0] ?? identifier).toLowerCase())
5359
+ const privacyLine = isContact
5360
+ ? ' This contact claim is PRIVATE to you: no other brain member can see it, and it is never rendered in the page. Your captured texts/mail from this contact will now propose this page.'
5361
+ : ''
5362
+ return { content: [{ type: 'text', text: `Routing identifier set on document ${out?.documentId ?? '?'}: ${set}.${healedLine} Future matching events will attach here too (body mentions alone will not).${privacyLine}` }] }
5310
5363
  },
5311
5364
  )
5312
5365
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theronap/agnoclast-mcp",
3
- "version": "0.9.224",
3
+ "version": "0.9.226",
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": {