@opennous/mcp 0.58.0 → 0.59.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 +40 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/mcp",
3
- "version": "0.58.0",
3
+ "version": "0.59.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
@@ -29,7 +29,7 @@ const APP_URL = () => (process.env.NOUS_APP_URL || "https://app.opennous.cloud")
29
29
  const OAUTH_CONNECT = { gmail: "gmail", gmail_oauth: "gmail", google: "gmail", "google-mail": "gmail", googlemail: "gmail", linkedin: "linkedin" };
30
30
  const connectLink = (slug) => `${APP_URL()}/settings?section=integrations&connect=${slug}`;
31
31
 
32
- export const SERVER_VERSION = "0.57.0";
32
+ export const SERVER_VERSION = "0.59.0";
33
33
 
34
34
  // ─── helpers ──────────────────────────────────────────────────────────────────
35
35
 
@@ -67,6 +67,24 @@ function whenLabel(type, ts) {
67
67
  }
68
68
 
69
69
  const fmtType = (p) => (p || "").replace(/^interaction\./, "").replace(/_/g, " ");
70
+ // The EVIDENCE line under a fact: who said it, their verbatim words, and where it
71
+ // came from. This is what makes a fact checkable instead of something the user has
72
+ // to take on faith — the rep reads the actual sentence rather than trusting a
73
+ // summary. Returns [] when there is nothing to show: facts recorded before evidence
74
+ // capture shipped, and facts with nothing quotable (an attendee list, a CRM field),
75
+ // so the block degrades quietly on historical data instead of looking broken.
76
+ function evidenceLines(f) {
77
+ const out = [];
78
+ const who = f.speaker ? String(f.speaker).trim() : "";
79
+ const quote = f.quote ? String(f.quote).trim() : "";
80
+ const where = [f.source, f.date ? new Date(f.date).toISOString().slice(0, 10) : null]
81
+ .filter(Boolean).join(" \u00b7 ");
82
+ if (quote) out.push(` ${who ? `${who} \u00b7 ` : ""}"${quote}"`);
83
+ else if (who) out.push(` ${who}`);
84
+ if (where && (quote || who)) out.push(` ${where}`);
85
+ return out;
86
+ }
87
+
70
88
  const fmtVal = (v) => (v != null && typeof v === "object") ? JSON.stringify(v) : String(v ?? "");
71
89
  const pct = (c) => `${Math.round((c ?? 0) * 100)}%`;
72
90
 
@@ -92,7 +110,7 @@ export function createServer() {
92
110
  // Reads: get_context · get_account · query (search folds in via facts:true) · score.
93
111
  // Identity: whoami (who the key acts as, scope, GTM role[s]) — the governance
94
112
  // substrate. record_signal folds into record; save_note is out. The `tool()` guard
95
- // keeps the surface locked to these seven — a stray non-primitive registration is
113
+ // keeps the surface locked to these eight — a stray non-primitive registration is
96
114
  // dropped, not exposed. See docs/revenue-plugin/README.md §2–3.
97
115
  const PLUGIN_TOOLS = new Set([
98
116
  "record", "record_insight", "get_context", "get_account", "query", "score", "whoami", "set_icp",
@@ -145,7 +163,10 @@ export function createServer() {
145
163
  if (ctx.facts?.length) {
146
164
  // Atomic memory — the durable, decision-relevant facts learned about them.
147
165
  lines.push(`FACTS (${ctx.facts.length} — durable memory about them):`);
148
- for (const f of ctx.facts) lines.push(` [${f.category}] ${f.content}${f.date ? ` (${relAge(f.date)})` : ""}`);
166
+ for (const f of ctx.facts) {
167
+ lines.push(` [${f.category}] ${f.content}${f.date ? ` (${relAge(f.date)})` : ""}`);
168
+ for (const e of evidenceLines(f)) lines.push(e);
169
+ }
149
170
  lines.push("");
150
171
  }
151
172
  if (ctx.claims?.length) {
@@ -261,7 +282,10 @@ export function createServer() {
261
282
  if (rec.facts?.length) {
262
283
  // Atomic memory — the durable, decision-relevant facts learned about them.
263
284
  lines.push(`FACTS (${rec.facts.length} — durable memory about them):`);
264
- for (const f of rec.facts) lines.push(` [${f.category}] ${f.content}${f.date ? ` (${relAge(f.date)})` : ""}`);
285
+ for (const f of rec.facts) {
286
+ lines.push(` [${f.category}] ${f.content}${f.date ? ` (${relAge(f.date)})` : ""}`);
287
+ for (const e of evidenceLines(f)) lines.push(e);
288
+ }
265
289
  lines.push("");
266
290
  }
267
291
  const docs = rec.documents ?? [];
@@ -357,8 +381,17 @@ export function createServer() {
357
381
  "about the contact — a preference, an objection, a competitor they mentioned, a general note — " +
358
382
  "use property:'intel' with a category: {kind:'state',property:'intel',value:{category:'objection'," +
359
383
  "content:'skeptical about market demand',label:'Market demand skepticism'}} (category ∈ " +
360
- "preference | objection | competitor | buying_signal | budget | timing | authority | general). " +
361
- "Insights about YOUR OWN business (product/positioning/market/buyer) go to record_insight, not here.",
384
+ "status_quo | goal | pain | objection | authority | budget | timeline | buying_signal | " +
385
+ "preference | competitor | discovery | relationship | general use the MOST specific one; " +
386
+ "'general' is a last resort, and it's 'timeline', never 'timing'). " +
387
+ "Insights about YOUR OWN business (product/positioning/market/buyer) go to record_insight, not here. " +
388
+ "CLOSING THE LOOP — when you RECOMMEND an action, write the recommendation down before it happens: " +
389
+ "{kind:'state',property:'decision.proposed',value:{decision_id:'<your id>',proposal:'send a follow-up " +
390
+ "naming the security objection',action_type:'email_send',category:'outreach'}}. When the human answers, " +
391
+ "record the verdict: property 'decision.accepted' | 'decision.edited' | 'decision.rejected' with the same " +
392
+ "decision_id (pass drafted_body + sent_body on accepted and the edit is measured for you). Then pass that " +
393
+ "same decision_id on the interaction observation when the action fires. That is what turns advice into " +
394
+ "learnable data — without it a send is an orphan nobody can grade.",
362
395
  {
363
396
  focus: z.string().describe("Email address, LinkedIn URL, domain, or entity UUID — a precise identifier, never a bare name"),
364
397
  observations: z.array(z.object({
@@ -371,6 +404,7 @@ export function createServer() {
371
404
  external_id: z.string().optional().describe("idempotency key, unique per (source). Re-recording the same one is a no-op. One raw item → many observations → give each a distinct id, e.g. '<itemId>:<property>'."),
372
405
  source_ref: z.string().optional().describe("pointer to the raw this came from, e.g. 'git://<repo>/raw/fireflies/2026-03-14-9182.md'. Keeps provenance without storing raw in Nous."),
373
406
  content_hash: z.string().optional().describe("sha256 of the raw file, so a later fetch can be verified untampered. Set when raw lives in git (not sent here)."),
407
+ decision_id: z.string().optional().describe("the recommendation this action came from. Stamp it on the interaction observation you record when the action fires (an email send, a LinkedIn message) and Nous attributes whatever comes back — reply, meeting, bounce, silence — to that decision."),
374
408
  })).describe("One or more observations to record"),
375
409
  },
376
410
  async ({ focus, observations }) => {