@opennous/mcp 0.59.0 → 0.61.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 +65 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/mcp",
3
- "version": "0.59.0",
3
+ "version": "0.61.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.59.0";
32
+ export const SERVER_VERSION = "0.60.0";
33
33
 
34
34
  // ─── helpers ──────────────────────────────────────────────────────────────────
35
35
 
@@ -105,15 +105,18 @@ export function createServer() {
105
105
  ],
106
106
  });
107
107
 
108
- // Nous is fully a revenue plugin: this server exposes EXACTLY the 7 primitives and
108
+ // Nous is fully a revenue plugin: this server exposes EXACTLY these primitives and
109
109
  // nothing else (the ~30 legacy tools were pruned). Writes: record · record_insight.
110
110
  // Reads: get_context · get_account · query (search folds in via facts:true) · score.
111
- // Identity: whoami (who the key acts as, scope, GTM role[s]) — the governance
112
- // substrate. record_signal folds into record; save_note is out. The `tool()` guard
113
- // keeps the surface locked to these eight a stray non-primitive registration is
114
- // dropped, not exposed. See docs/revenue-plugin/README.md §2–3.
111
+ // ICP: set_icp writes the hypothesis; record_closed_deals trains it on real
112
+ // won/lost outcomes (contrastive lift). Identity: whoami (who the key acts as,
113
+ // scope, GTM role[s]) the governance substrate. record_signal folds into record;
114
+ // save_note is out. The `tool()` guard keeps the surface locked to these nine — a
115
+ // stray non-primitive registration is dropped, not exposed. See
116
+ // docs/revenue-plugin/README.md §2–3.
115
117
  const PLUGIN_TOOLS = new Set([
116
118
  "record", "record_insight", "get_context", "get_account", "query", "score", "whoami", "set_icp",
119
+ "record_closed_deals",
117
120
  ]);
118
121
  const _tool = server.tool.bind(server);
119
122
  const tool = (name, ...rest) => {
@@ -387,7 +390,11 @@ export function createServer() {
387
390
  "Insights about YOUR OWN business (product/positioning/market/buyer) go to record_insight, not here. " +
388
391
  "CLOSING THE LOOP — when you RECOMMEND an action, write the recommendation down before it happens: " +
389
392
  "{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, " +
393
+ "naming the security objection',rationale:'the security review is the real blocker at this stage', " +
394
+ "evidence_ids:['<claim/note id you reasoned from>'],action_type:'email_send',category:'outreach'}}. " +
395
+ "Keep `proposal` (WHAT you are doing) apart from `rationale` (WHY you think it works), and list the " +
396
+ "facts you actually reasoned from in `evidence_ids` — that is what lets Nous learn which KINDS of " +
397
+ "evidence are worth acting on, not just which actions worked. When the human answers, " +
391
398
  "record the verdict: property 'decision.accepted' | 'decision.edited' | 'decision.rejected' with the same " +
392
399
  "decision_id (pass drafted_body + sent_body on accepted and the edit is measured for you). Then pass that " +
393
400
  "same decision_id on the interaction observation when the action fires. That is what turns advice into " +
@@ -480,6 +487,57 @@ export function createServer() {
480
487
  }
481
488
  );
482
489
 
490
+ // ===========================================================================
491
+ // TOOL: record_closed_deals — POST /v2/workspace/closed-deals
492
+ // Train the ICP on REAL outcomes. set_icp writes the hypothesis; this upgrades
493
+ // it to an outcome-graded model: feed the won and lost domains (the CRM/Stripe
494
+ // closed cohorts), the engine runs contrastive lift — what separates wins from
495
+ // losses — links known contacts, resolves their open predictions with the real
496
+ // outcome, and re-scores every open account. The onboarding + win-loss payoff.
497
+ // ===========================================================================
498
+ tool(
499
+ "record_closed_deals",
500
+ "Train this workspace's ICP on REAL closed deals. Pass the won and lost deals (each a bare " +
501
+ "domain, or an object {domain, amount, currency, closed_at}). The engine enriches each domain, " +
502
+ "links the contacts you already have there, resolves their open ICP predictions with the real " +
503
+ "outcome, runs contrastive lift (the signals that separate wins from losses), and re-scores every " +
504
+ "open account against the upgraded model. Use it in onboarding once the backfill has landed the " +
505
+ "closed cohorts (pull them with query scope.property:'stage' — accounts at 'closed_won' and " +
506
+ "'closed_lost'), and whenever new deals close. Needs at least one deal; both sides (some won AND " +
507
+ "some lost) make the lift trustworthy — one side alone is directional only. The ICP is the one " +
508
+ "company model, so this is an admin/founder action.",
509
+ {
510
+ won: z.array(CLOSED_DEAL).optional().describe("Closed-WON deals — bare domains or {domain, amount, currency, closed_at}. Pull from accounts at stage 'closed_won'."),
511
+ lost: z.array(CLOSED_DEAL).optional().describe("Closed-LOST deals — same shape. Pull from accounts at stage 'closed_lost'."),
512
+ },
513
+ async ({ won = [], lost = [] }) => {
514
+ const r = await post("/v2/workspace/closed-deals", { won, lost });
515
+ if (r.need_more_deals || r.error === "need_more_deals") {
516
+ return { content: [{ type: "text", text:
517
+ "Need at least one closed deal (a won or lost domain) to train the ICP. Pull the closed cohorts with query scope.property:'stage', then pass their domains." }] };
518
+ }
519
+ const lines = [
520
+ `ICP trained on real outcomes — ${r.won || 0} won · ${r.lost || 0} lost` +
521
+ (r.enriched ? ` · ${r.enriched} companies enriched` : "") +
522
+ (r.surfaced ? ` · ${r.surfaced} predictions resolved` : "") + ".",
523
+ ];
524
+ if ((r.discovered || []).length) {
525
+ lines.push("", "Signals the model learned (contrastive lift, won vs lost):");
526
+ for (const d of r.discovered) {
527
+ const dir = (d.weight ?? 0) > 0 ? "+" : "";
528
+ lines.push(` • ${d.label} [${dir}${d.weight}]${d.note ? ` — ${d.note}` : ""}`);
529
+ }
530
+ }
531
+ if ((r.linked || []).length) {
532
+ const names = r.linked.map(l => l.name).filter(Boolean);
533
+ lines.push("", `Linked ${r.linked.length} known contact${r.linked.length !== 1 ? "s" : ""} to these deals` +
534
+ (names.length ? `: ${names.slice(0, 8).join(", ")}${names.length > 8 ? "…" : ""}` : "") + ".");
535
+ }
536
+ lines.push("", "Open accounts have been re-scored. score / get_context now reflect the outcome-graded ICP.");
537
+ return { content: [{ type: "text", text: lines.join("\n") }] };
538
+ }
539
+ );
540
+
483
541
  // ===========================================================================
484
542
  // TOOL: query — POST /v2/query
485
543
  // Retrieve a corpus of activity across many people. You do the analysis.