@opennous/mcp 0.58.1 → 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 +36 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/mcp",
3
- "version": "0.58.1",
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
 
@@ -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 ?? [];
@@ -360,7 +384,14 @@ export function createServer() {
360
384
  "status_quo | goal | pain | objection | authority | budget | timeline | buying_signal | " +
361
385
  "preference | competitor | discovery | relationship | general — use the MOST specific one; " +
362
386
  "'general' is a last resort, and it's 'timeline', never 'timing'). " +
363
- "Insights about YOUR OWN business (product/positioning/market/buyer) go to record_insight, not here.",
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.",
364
395
  {
365
396
  focus: z.string().describe("Email address, LinkedIn URL, domain, or entity UUID — a precise identifier, never a bare name"),
366
397
  observations: z.array(z.object({
@@ -373,6 +404,7 @@ export function createServer() {
373
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>'."),
374
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."),
375
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."),
376
408
  })).describe("One or more observations to record"),
377
409
  },
378
410
  async ({ focus, observations }) => {