@opennous/mcp 0.23.0 → 0.25.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 +66 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opennous/mcp",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Nous MCP Server — Customer graph for GTM agents.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
package/src/server.js CHANGED
@@ -24,6 +24,7 @@
24
24
  * get_workspace_status — what's set up in this workspace + a ranked next_steps list (call first)
25
25
  * set_workspace_profile— agent-driven onboarding: set the workspace's name, site, type, ICP
26
26
  * build_scoring_model — build/rebuild the ICP scoring model from the recorded GTM context
27
+ * record_closed_deals — build the ICP model from real closed-won/lost deals (contrastive lift)
27
28
  * connect_integration — connect a key-based integration (Apollo, Prospeo, HubSpot, …)
28
29
  * configure_crm_sync — set CRM sync rules (auto-sync, create policy, hygiene cadence)
29
30
  * set_trigger — create an outbound event trigger (webhook); list_triggers reads them
@@ -37,7 +38,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
37
38
  import { z } from "zod";
38
39
  import { get, post } from "./client.js";
39
40
 
40
- export const SERVER_VERSION = "0.23.0";
41
+ export const SERVER_VERSION = "0.25.0";
41
42
 
42
43
  // ─── helpers ──────────────────────────────────────────────────────────────────
43
44
 
@@ -506,13 +507,21 @@ export function createServer() {
506
507
  // ===========================================================================
507
508
  server.tool(
508
509
  "get_workspace_status",
509
- "See the whole setup state of this workspace in one call and what to set up next. Nous is " +
510
- "operated by you, the agent, not by a human clicking through the app: call this at the start of " +
511
- "a session to learn whether the workspace is onboarded, whether the GTM playbook (ICP model) is " +
512
- "built, which integrations are connected, whether CRM sync is configured, and whether webhooks/" +
513
- "triggers are live. Returns a ranked NEXT STEPS list walk the user through whatever is missing " +
514
- "(onboard them with set_workspace_profile, build the playbook with update_gtm_profile, connect " +
515
- "their tools). Use this before offering to set anything up.",
510
+ "See the whole setup state of this workspace in one call, and what to do next. Nous is operated " +
511
+ "by you, the agent: call this at the start of a session. It returns a ranked NEXT STEPS list — " +
512
+ "walk the user through it top-down. The onboarding sequence, in order: " +
513
+ "(1) profile (set_workspace_profile); " +
514
+ "(2) connect core channelsGmail/email, LinkedIn, and a meeting note-taker (Fireflies/Fathom/" +
515
+ "Calendly); (3) connect enrichment/outbound (Prospeo/Apollo/Instantly); (4) set up webhooks for " +
516
+ "the connected tools so their events flow in; (5) import first records (CSV from the CRM) on the " +
517
+ "Accounts page, then backfill enrichment; (6) build the GTM playbook (update_gtm_profile + " +
518
+ "build_scoring_model, asking for closed-won/lost domains). " +
519
+ "KNOW THE CONSTRAINTS: Gmail uses Google OAuth and LinkedIn has NO public API (Nous connects it " +
520
+ "natively via Unipile) — you CANNOT connect those yourself; tell the user to set them up on the " +
521
+ "Integrations page. Key-based tools (Prospeo, Apollo, Instantly, HubSpot token) you CAN connect " +
522
+ "with connect_integration. CSV import and CRM-page actions are done by the user in the app — guide " +
523
+ "them. Respect PLAN: never push a feature the plan doesn't include (e.g. CRM sync on free). " +
524
+ "Recommend, don't dump — surface the next 1-2 steps, not all of them at once.",
516
525
  {},
517
526
  async () => {
518
527
  const s = await get("/v2/workspace/status");
@@ -539,6 +548,11 @@ export function createServer() {
539
548
  }
540
549
  lines.push(` ${mark(setup.enrichment?.connected)} Enrichment${setup.enrichment?.provider ? `: ${setup.enrichment.provider}` : ""}`);
541
550
  lines.push(` ${mark((setup.webhooks?.count ?? 0) > 0 || (setup.triggers?.count ?? 0) > 0)} Events — ${setup.webhooks?.count ?? 0} webhook(s), ${setup.triggers?.count ?? 0} trigger(s)`);
551
+ const rec = setup.recommended ?? {};
552
+ lines.push("");
553
+ lines.push("RECOMMENDED CHANNELS (connect these first):");
554
+ lines.push(` ${mark(rec.email)} Email / Gmail ${mark(rec.linkedin)} LinkedIn ${mark(rec.meeting_notetaker)} Meeting note-taker`);
555
+ lines.push(` Records imported: ${setup.records?.count ?? 0}`);
542
556
 
543
557
  if (s.next_steps?.length) {
544
558
  lines.push("");
@@ -614,9 +628,9 @@ export function createServer() {
614
628
  "signals so accounts get scored for fit. If a model already exists it is left alone unless you " +
615
629
  "pass force:true (use that when the GTM context has changed and the model should be rebuilt). If " +
616
630
  "it reports no GTM context yet, record some with update_gtm_profile first, then call this again. " +
617
- "When building the playbook during onboarding, also ASK the user for a few closed-WON customer " +
618
- "domains and closed-LOST domainsreal outcomes sharpen the ICP. Record them with update_gtm_profile " +
619
- "(e.g. section 'ICP': 'Closed-won: acme.com, globex.com; closed-lost: tinyco.io') so the model reflects who actually buys.",
631
+ "STRONGER than this tool: if the user can name a few closed-WON and closed-LOST customer domains, " +
632
+ "call record_closed_deals instead (or as well) it trains the model on real outcomes via " +
633
+ "contrastive lift, which beats a model inferred from a description.",
620
634
  {
621
635
  force: z.boolean().optional()
622
636
  .describe("Rebuild the model even if one already exists — use when the GTM context has changed."),
@@ -646,6 +660,47 @@ export function createServer() {
646
660
  }
647
661
  );
648
662
 
663
+ // ===========================================================================
664
+ // TOOL: record_closed_deals — POST /v2/workspace/closed-deals
665
+ // Build the ICP model from REAL outcomes via contrastive lift (won vs lost).
666
+ // ===========================================================================
667
+ server.tool(
668
+ "record_closed_deals",
669
+ "Build (or sharpen) the ICP scoring model from the user's REAL closed deals. Pass closed-WON " +
670
+ "customer domains and closed-LOST domains; Nous enriches each, links the contacts you already " +
671
+ "have there, and runs contrastive lift (what's true of winners but not losers) to discover the " +
672
+ "signals that actually predict revenue — then re-scores open accounts. This is the strongest way " +
673
+ "to build the playbook: a model trained on who actually bought beats one inferred from a " +
674
+ "description. Ask the user for a handful of each (even 3-5 won + 3-5 lost helps). Domains only " +
675
+ "(e.g. 'acme.com'), no scheme.",
676
+ {
677
+ won: z.array(z.string()).optional().describe("Closed-won customer domains, e.g. ['acme.com','globex.com']."),
678
+ lost: z.array(z.string()).optional().describe("Closed-lost domains, e.g. ['tinyco.io']."),
679
+ },
680
+ async ({ won, lost }) => {
681
+ try {
682
+ const r = await post("/v2/workspace/closed-deals", { won: won ?? [], lost: lost ?? [] });
683
+ const disc = r.discovered ?? [];
684
+ const lines = [
685
+ `Learned from ${r.won ?? 0} won + ${r.lost ?? 0} lost deal${(r.won ?? 0) + (r.lost ?? 0) === 1 ? "" : "s"} ` +
686
+ `(${r.enriched ?? 0} enriched, ${r.mode === "winners" ? "winner-signal" : "contrastive-lift"} mode).`,
687
+ ];
688
+ if (disc.length) {
689
+ lines.push("", "Signals discovered:");
690
+ for (const d of disc) lines.push(` • ${d.label} (weight ${d.weight})${d.note ? ` — ${d.note}` : ""}`);
691
+ }
692
+ lines.push("", "The model updated and open accounts were re-scored. See the GTM Context page.");
693
+ return { content: [{ type: "text", text: lines.join("\n").trim() }] };
694
+ } catch (e) {
695
+ const msg = String(e?.message ?? e);
696
+ if (msg.includes("need_more_deals")) {
697
+ return { content: [{ type: "text", text: "Give me at least one closed-won or closed-lost domain to learn from." }] };
698
+ }
699
+ throw e;
700
+ }
701
+ }
702
+ );
703
+
649
704
  // ===========================================================================
650
705
  // TOOL: connect_integration — POST /v2/workspace/integrations
651
706
  // The agent connects a KEY-BASED integration for the user (no clicking through