@opennous/mcp 0.48.0 → 0.49.0

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/package.json +1 -1
  2. package/src/server.js +45 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/mcp",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Nous — the Context Graph for AI Agents.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/server.js CHANGED
@@ -17,7 +17,7 @@
17
17
  * WRITE record · record_signal · save_note · propose_vault_file · merge_contacts ·
18
18
  * sync_foundation
19
19
  * ACT send_linkedin_message
20
- * CORRECT retract_observation · delete_note
20
+ * CORRECT retract_observation · delete_note · unmerge_contacts
21
21
  * RUN get_workspace_status · set_workspace_profile · build_icp_model · train_icp_model ·
22
22
  * sync_icp · export_icp_model · connect_integration · configure_crm_sync ·
23
23
  * sync_crm_now · scrape_engagers · get_routing_preferences
@@ -27,7 +27,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
27
27
  import { z } from "zod";
28
28
  import { get, post, del } from "./client.js";
29
29
 
30
- export const SERVER_VERSION = "0.48.0";
30
+ export const SERVER_VERSION = "0.49.0";
31
31
 
32
32
  // ─── helpers ──────────────────────────────────────────────────────────────────
33
33
 
@@ -406,12 +406,54 @@ export function createServer() {
406
406
  (r.relationships_repointed || r.relationships_removed)
407
407
  ? ` relationships: ${r.relationships_repointed} re-pointed, ${r.relationships_removed} pruned` : null,
408
408
  moved ? ` re-pointed: ${moved}` : null,
409
- `The duplicate is now a reversible tombstone (merged into the survivor).`,
409
+ `Reversible: if this was wrong, unmerge_contacts with drop_id "${r.drop_id}" puts it all back.`,
410
410
  ].filter(Boolean);
411
411
  return { content: [{ type: "text", text: lines.join("\n") }] };
412
412
  }
413
413
  );
414
414
 
415
+ server.tool(
416
+ "unmerge_contacts",
417
+ "REVERSE a merge — split a wrongly-merged duplicate back out into its own account. Identify the " +
418
+ "merge to undo either by `drop_id` (the tombstone's id, exactly as merge_contacts reported it) or by " +
419
+ "`keep` (an identifier for the survivor — undoes the MOST RECENT merge into it). Every re-pointed " +
420
+ "identifier, claim, observation, relationship and the record itself go back where they were. " +
421
+ "Forward-only: it can only reverse merges made after reversible-merge tracking existed — an older " +
422
+ "merge returns a clear 'not reversible'. Use this when two DIFFERENT people were merged by mistake; " +
423
+ "it is not for editing a correctly-merged account.",
424
+ {
425
+ drop_id: z.string().optional().describe("The merged-away entity's id, from the merge_contacts result. Provide this OR keep."),
426
+ keep: z.string().optional().describe("The survivor (email, LinkedIn URL, entity UUID, or name) — undoes the most recent un-reversed merge into it."),
427
+ },
428
+ async ({ drop_id, keep }) => {
429
+ if (!drop_id && !keep) {
430
+ return { content: [{ type: "text", text: "Give me the drop_id from the merge result, or the `keep` survivor whose last merge to undo." }] };
431
+ }
432
+ try {
433
+ const r = await post("/v2/accounts/unmerge", { drop_id, keep });
434
+ if (r.status === "ambiguous") {
435
+ const opts = (r.candidates ?? []).map(c => ` • ${c.name ?? "(unnamed)"} [${c.entity_id}]`).join("\n");
436
+ return { content: [{ type: "text", text: `"${keep}" matches several people. Re-call unmerge_contacts with one of these entity ids as keep:\n${opts}` }] };
437
+ }
438
+ const lines = [
439
+ `Un-merged — ${r.drop_id} is its own account again.`,
440
+ ` identifiers restored: ${r.identifiers}, claims: ${r.claims}, observations: ${r.observations}, relationships: ${r.relationships}`,
441
+ r.contact_restored ? ` the contact record was recreated.` : null,
442
+ ].filter(Boolean);
443
+ return { content: [{ type: "text", text: lines.join("\n") }] };
444
+ } catch (e) {
445
+ const msg = /not_reversible/.test(e.message)
446
+ ? "That merge can't be reversed — it predates reversible-merge tracking, or it was already un-merged."
447
+ : /no_reversible_merge/.test(e.message)
448
+ ? "No un-reversed merge on that survivor to undo."
449
+ : /entity_not_found/.test(e.message)
450
+ ? "Couldn't find that survivor — check the keep identifier."
451
+ : `Couldn't un-merge: ${e.message}`;
452
+ return { content: [{ type: "text", text: msg }] };
453
+ }
454
+ }
455
+ );
456
+
415
457
  // ===========================================================================
416
458
  // TOOL: record — POST /v2/observations
417
459
  // The single write verb. You observe — Nous derives the updated facts.