@opennous/mcp 0.42.0 → 0.43.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 +74 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/mcp",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "Nous — the Context Graph for AI Agents.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
package/src/server.js CHANGED
@@ -89,9 +89,9 @@ const pct = (c) => `${Math.round((c ?? 0) * 100)}%`;
89
89
  // Gong/Granola). Returned by get_routing_preferences; the agent writes it into
90
90
  // the user's CLAUDE.md (Claude Code) or the user pastes it into claude.ai org
91
91
  // preferences. Mirrors the Install page's "short" version.
92
- const ROUTING_PREFERENCES = `# Nous is this workspace's customer graph
92
+ const ROUTING_PREFERENCES = `# Nous is this workspace's GTM context graph
93
93
 
94
- Nous is our customer graph for GTM. It resolves every person, conversation, and
94
+ Nous is our context graph for GTM. It resolves every person, conversation, and
95
95
  touchpoint across our GTM tool stack into one account record, with each fact's
96
96
  confidence and freshness, the full interaction timeline, a 0-100 ICP fit score on
97
97
  every account, plus our own ICP, positioning, and pricing. Agents read engineered
@@ -249,11 +249,27 @@ export function createServer() {
249
249
  "get_account",
250
250
  "Get the full account record for a person or company — the durable FACTS we've learned about them " +
251
251
  "(their atomic memory: budget, authority, pain, stack, plans), every attribute (claim) with its " +
252
- "confidence and freshness, plus the recent activity timeline. Pass an email or entity UUID. " +
253
- "For a task-specific, ranked view, prefer get_context.",
254
- { id: z.string().describe("Email address or entity UUID") },
255
- async ({ id }) => {
256
- const rec = await get(`/v2/accounts/${encodeURIComponent(id)}`);
252
+ "confidence and freshness, plus what they actually SAID and did, ranked by how much it tells you. " +
253
+ "Pass an email or entity UUID, and the intent you're working toward so the record is shaped for it.",
254
+ {
255
+ id: z.string().describe("Email address or entity UUID"),
256
+ intent: z
257
+ .enum(["meeting_prep", "call_prep", "account_review", "follow_up", "draft_email"])
258
+ .optional()
259
+ .describe(
260
+ "What you're about to do. Shapes how much of their history comes back: a meeting brief wants " +
261
+ "the conversation in detail, an email draft wants one hook. Defaults to account_review.",
262
+ ),
263
+ },
264
+ async ({ id, intent }) => {
265
+ // Ask for the RANKED record, not the raw one.
266
+ //
267
+ // The timeline this tool used to print was chronological and contentless —
268
+ // "3d ago email_sent" — which tells an agent that something happened and
269
+ // nothing about what. Ranked activity carries the source and the substance,
270
+ // so the model reads what was actually said instead of a list of event names.
271
+ const q = new URLSearchParams({ intent: intent ?? "account_review", compress: "1" });
272
+ const rec = await get(`/v2/accounts/${encodeURIComponent(id)}?${q}`);
257
273
  const lines = [`${rec.type} · ${rec.entity_id}`, ""];
258
274
 
259
275
  if (rec.icp) {
@@ -275,13 +291,37 @@ export function createServer() {
275
291
  }
276
292
  lines.push("");
277
293
  }
278
- const obs = rec.recent_observations ?? [];
279
- if (obs.length) {
294
+ // What they actually said and did — the most telling first, each with the
295
+ // system it came from, so a claim in the answer can always be traced back.
296
+ const activity = rec.key_activity ?? [];
297
+ if (activity.length) {
298
+ lines.push(`WHAT HAPPENED (${activity.length} most telling):`);
299
+ for (const a of activity) {
300
+ const when = a.when ? relAge(a.when) : "";
301
+ const head = ` ${a.what}${a.source ? ` · ${a.source}` : ""}${when ? ` · ${when}` : ""}`;
302
+ lines.push(a.detail ? `${head}\n ${a.detail}` : head);
303
+ }
304
+ lines.push("");
305
+ }
306
+
307
+ // Say what was left out, and why. An agent that is handed 18 of 300
308
+ // interactions and does not know it will happily conclude that nothing else
309
+ // ever happened.
310
+ const sum = rec.activity_summary;
311
+ if (sum?.note) lines.push(sum.note);
312
+ else if (sum?.total_observations) {
313
+ lines.push(`${sum.total_observations} interactions on record.`);
314
+ }
315
+
316
+ // Fall back to the raw timeline if an older API didn't rank anything.
317
+ if (!activity.length && rec.recent_observations?.length) {
318
+ const obs = rec.recent_observations;
280
319
  lines.push(`TIMELINE (${obs.length}):`);
281
320
  for (const o of obs.slice(0, 30)) {
282
321
  lines.push(` ${whenLabel(o.property, o.observed_at)} ${fmtType(o.property)}`);
283
322
  }
284
323
  }
324
+
285
325
  return { content: [{ type: "text", text: lines.join("\n").trim() }] };
286
326
  }
287
327
  );
@@ -507,9 +547,15 @@ export function createServer() {
507
547
  return { content: [{ type: "text", text: "Nothing needs attention right now." }] };
508
548
  }
509
549
  // Upcoming meetings carry a `when` — render the absolute local date+time.
550
+ //
551
+ // Each item also names where it came from: the calendar holding the call, the
552
+ // transcript the promise was captured from. An agent that can cite the call
553
+ // someone made a promise ON is making an argument; one that just asserts the
554
+ // promise is asking to be trusted.
510
555
  const lines = r.items.map(it => {
511
556
  const when = it.when ? `${fmtWhen(it.when)} — ` : "";
512
- return ` ${when}${it.entity_name ?? it.entity_id} ${it.what}\n → ${it.suggested_action}`;
557
+ const from = it.source ? ` [${it.source}]` : "";
558
+ return ` ${when}${it.entity_name ?? it.entity_id} — ${it.what}${from}\n → ${it.suggested_action}`;
513
559
  });
514
560
  return { content: [{ type: "text", text: `Needs attention (${r.items.length}):\n${lines.join("\n")}` }] };
515
561
  }
@@ -638,6 +684,11 @@ export function createServer() {
638
684
  "whenever you edit a policy file in the repo (e.g. references/voice.md, outreach rules), passing the " +
639
685
  "file's new content and its path, so Nous mirrors it and every other agent obeys the same rules. An " +
640
686
  "edited playbook file that isn't synced is silently inert — other agents keep reading the old rules. " +
687
+ "MIRROR, DO NOT REWRITE: when the user already has a playbook file, sync it AS-IS. Their file is the " +
688
+ "author and Nous is the mirror — always pass file_path so the next sync knows where an in-app edit " +
689
+ "lands. 'Improving' their wording on the way through means the copy in Nous silently disagrees with " +
690
+ "the copy in their repo, and they will trust neither. If a file looks wrong, SAY SO; don't fix it in " +
691
+ "transit. " +
641
692
  "(For the ICP/context files specifically, sync_icp is the sync — use that one.)",
642
693
  syncPlaybookSchema, syncPlaybookHandler);
643
694
 
@@ -752,7 +803,19 @@ export function createServer() {
752
803
 
753
804
  const mark = (b) => (b ? "✓" : "✗");
754
805
  lines.push("SETUP:");
755
- lines.push(` ${mark(setup.onboarding?.done)} Onboarding${setup.onboarding?.done ? "" : `missing ${(setup.onboarding?.missing ?? []).join(", ") || "details"}`}`);
806
+ // The ICP first, because it IS the gate a workspace without one is not set up, no
807
+ // matter how many integrations are green. If it's mirrored from a file in their repo,
808
+ // say so and say where: that file is the author, and editing anything else is a way of
809
+ // losing their work on the next sync.
810
+ const icp = setup.icp ?? {};
811
+ lines.push(
812
+ ` ${mark(icp.done)} ICP${icp.done
813
+ ? (icp.source === "claude_code" && icp.file_path
814
+ ? ` — mirrored from ${icp.file_path} (their repo is the author; edit the FILE, then sync)`
815
+ : " — authored in Nous")
816
+ : " — MISSING. The workspace is not set up until this exists. Scan their repo before you ask them anything."}`
817
+ );
818
+ lines.push(` ${mark(setup.onboarding?.done)} Profile${setup.onboarding?.done ? "" : ` — missing ${(setup.onboarding?.missing ?? []).join(", ") || "details"}`}`);
756
819
  lines.push(` ${mark(setup.gtm_playbook?.done)} GTM playbook${setup.gtm_playbook?.model ? " (scoring model live)" : ""}${setup.gtm_playbook?.stale_facts ? ` · ${setup.gtm_playbook.stale_facts} stale fact(s)` : ""}`);
757
820
  if (setup.icp_sync) {
758
821
  const sy = setup.icp_sync;