@opennous/mcp 0.60.0 → 0.62.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.
- package/package.json +4 -3
- package/src/server.js +83 -20
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennous/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nous
|
|
3
|
+
"version": "0.62.0",
|
|
4
|
+
"description": "Nous \u2014 the Context Graph for AI Agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"start": "node src/index.js",
|
|
22
22
|
"dev:http": "node --watch src/http.js",
|
|
23
23
|
"start:http": "node src/http.js",
|
|
24
|
-
"typecheck": "echo 'no ts in mcp
|
|
24
|
+
"typecheck": "echo 'no ts in mcp \u2014 skipping'",
|
|
25
|
+
"test": "node --test tests/*.test.mjs"
|
|
25
26
|
},
|
|
26
27
|
"keywords": [
|
|
27
28
|
"mcp",
|
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.
|
|
32
|
+
export const SERVER_VERSION = "0.62.0";
|
|
33
33
|
|
|
34
34
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
35
35
|
|
|
@@ -91,6 +91,24 @@ const pct = (c) => `${Math.round((c ?? 0) * 100)}%`;
|
|
|
91
91
|
|
|
92
92
|
// ─── factory ──────────────────────────────────────────────────────────────────
|
|
93
93
|
|
|
94
|
+
// A closed deal as the agent may supply it. A bare domain is the original shape and still
|
|
95
|
+
// works; the object form carries the money and the date, which the forecast needs. Widened
|
|
96
|
+
// rather than replaced so nothing that already calls this breaks.
|
|
97
|
+
//
|
|
98
|
+
// Module scope, not inside createServer(). It was declared below its own first use, and a
|
|
99
|
+
// `const` in the temporal dead zone throws on every request — the hosted server crashed on
|
|
100
|
+
// each call with "Cannot access 'CLOSED_DEAL' before initialization". Out here it is also
|
|
101
|
+
// built once instead of per request, since createServer() runs for every incoming call.
|
|
102
|
+
const CLOSED_DEAL = z.union([
|
|
103
|
+
z.string(),
|
|
104
|
+
z.object({
|
|
105
|
+
domain: z.string().describe("Company domain, e.g. 'acme.com' — no scheme."),
|
|
106
|
+
amount: z.number().optional().describe("Deal value in major units (48000, not 4800000)."),
|
|
107
|
+
currency: z.string().optional().describe("ISO code or symbol. Defaults to the workspace currency."),
|
|
108
|
+
closed_at: z.string().optional().describe("When it actually closed, ISO date. Drives every cycle-time measurement, so pass the real date rather than today's."),
|
|
109
|
+
}),
|
|
110
|
+
]);
|
|
111
|
+
|
|
94
112
|
export function createServer() {
|
|
95
113
|
const server = new McpServer({
|
|
96
114
|
name: "nous",
|
|
@@ -105,15 +123,18 @@ export function createServer() {
|
|
|
105
123
|
],
|
|
106
124
|
});
|
|
107
125
|
|
|
108
|
-
// Nous is fully a revenue plugin: this server exposes EXACTLY
|
|
126
|
+
// Nous is fully a revenue plugin: this server exposes EXACTLY these primitives and
|
|
109
127
|
// nothing else (the ~30 legacy tools were pruned). Writes: record · record_insight.
|
|
110
128
|
// Reads: get_context · get_account · query (search folds in via facts:true) · score.
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
//
|
|
129
|
+
// ICP: set_icp writes the hypothesis; record_closed_deals trains it on real
|
|
130
|
+
// won/lost outcomes (contrastive lift). Identity: whoami (who the key acts as,
|
|
131
|
+
// scope, GTM role[s]) — the governance substrate. record_signal folds into record;
|
|
132
|
+
// save_note is out. The `tool()` guard keeps the surface locked to these nine — a
|
|
133
|
+
// stray non-primitive registration is dropped, not exposed. See
|
|
134
|
+
// docs/revenue-plugin/README.md §2–3.
|
|
115
135
|
const PLUGIN_TOOLS = new Set([
|
|
116
136
|
"record", "record_insight", "get_context", "get_account", "query", "score", "whoami", "set_icp",
|
|
137
|
+
"record_closed_deals",
|
|
117
138
|
]);
|
|
118
139
|
const _tool = server.tool.bind(server);
|
|
119
140
|
const tool = (name, ...rest) => {
|
|
@@ -387,8 +408,12 @@ export function createServer() {
|
|
|
387
408
|
"Insights about YOUR OWN business (product/positioning/market/buyer) go to record_insight, not here. " +
|
|
388
409
|
"CLOSING THE LOOP — when you RECOMMEND an action, write the recommendation down before it happens: " +
|
|
389
410
|
"{kind:'state',property:'decision.proposed',value:{decision_id:'<your id>',proposal:'send a follow-up " +
|
|
390
|
-
"naming the security objection',rationale:'the security review
|
|
411
|
+
"naming the security objection',proposal_body:'<the exact draft>',rationale:'the security review " +
|
|
412
|
+
"is the real blocker at this stage', " +
|
|
391
413
|
"evidence_ids:['<claim/note id you reasoned from>'],action_type:'email_send',category:'outreach'}}. " +
|
|
414
|
+
"`proposal_body` is the draft itself — hashed and discarded, never stored, and the only way to tell " +
|
|
415
|
+
"later whether the human sent it as written or rewrote it first. Omit it and the decision reports as " +
|
|
416
|
+
"sent-verbatim whatever happened. " +
|
|
392
417
|
"Keep `proposal` (WHAT you are doing) apart from `rationale` (WHY you think it works), and list the " +
|
|
393
418
|
"facts you actually reasoned from in `evidence_ids` — that is what lets Nous learn which KINDS of " +
|
|
394
419
|
"evidence are worth acting on, not just which actions worked. When the human answers, " +
|
|
@@ -484,6 +509,57 @@ export function createServer() {
|
|
|
484
509
|
}
|
|
485
510
|
);
|
|
486
511
|
|
|
512
|
+
// ===========================================================================
|
|
513
|
+
// TOOL: record_closed_deals — POST /v2/workspace/closed-deals
|
|
514
|
+
// Train the ICP on REAL outcomes. set_icp writes the hypothesis; this upgrades
|
|
515
|
+
// it to an outcome-graded model: feed the won and lost domains (the CRM/Stripe
|
|
516
|
+
// closed cohorts), the engine runs contrastive lift — what separates wins from
|
|
517
|
+
// losses — links known contacts, resolves their open predictions with the real
|
|
518
|
+
// outcome, and re-scores every open account. The onboarding + win-loss payoff.
|
|
519
|
+
// ===========================================================================
|
|
520
|
+
tool(
|
|
521
|
+
"record_closed_deals",
|
|
522
|
+
"Train this workspace's ICP on REAL closed deals. Pass the won and lost deals (each a bare " +
|
|
523
|
+
"domain, or an object {domain, amount, currency, closed_at}). The engine enriches each domain, " +
|
|
524
|
+
"links the contacts you already have there, resolves their open ICP predictions with the real " +
|
|
525
|
+
"outcome, runs contrastive lift (the signals that separate wins from losses), and re-scores every " +
|
|
526
|
+
"open account against the upgraded model. Use it in onboarding once the backfill has landed the " +
|
|
527
|
+
"closed cohorts (pull them with query scope.property:'stage' — accounts at 'closed_won' and " +
|
|
528
|
+
"'closed_lost'), and whenever new deals close. Needs at least one deal; both sides (some won AND " +
|
|
529
|
+
"some lost) make the lift trustworthy — one side alone is directional only. The ICP is the one " +
|
|
530
|
+
"company model, so this is an admin/founder action.",
|
|
531
|
+
{
|
|
532
|
+
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'."),
|
|
533
|
+
lost: z.array(CLOSED_DEAL).optional().describe("Closed-LOST deals — same shape. Pull from accounts at stage 'closed_lost'."),
|
|
534
|
+
},
|
|
535
|
+
async ({ won = [], lost = [] }) => {
|
|
536
|
+
const r = await post("/v2/workspace/closed-deals", { won, lost });
|
|
537
|
+
if (r.need_more_deals || r.error === "need_more_deals") {
|
|
538
|
+
return { content: [{ type: "text", text:
|
|
539
|
+
"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." }] };
|
|
540
|
+
}
|
|
541
|
+
const lines = [
|
|
542
|
+
`ICP trained on real outcomes — ${r.won || 0} won · ${r.lost || 0} lost` +
|
|
543
|
+
(r.enriched ? ` · ${r.enriched} companies enriched` : "") +
|
|
544
|
+
(r.surfaced ? ` · ${r.surfaced} predictions resolved` : "") + ".",
|
|
545
|
+
];
|
|
546
|
+
if ((r.discovered || []).length) {
|
|
547
|
+
lines.push("", "Signals the model learned (contrastive lift, won vs lost):");
|
|
548
|
+
for (const d of r.discovered) {
|
|
549
|
+
const dir = (d.weight ?? 0) > 0 ? "+" : "";
|
|
550
|
+
lines.push(` • ${d.label} [${dir}${d.weight}]${d.note ? ` — ${d.note}` : ""}`);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if ((r.linked || []).length) {
|
|
554
|
+
const names = r.linked.map(l => l.name).filter(Boolean);
|
|
555
|
+
lines.push("", `Linked ${r.linked.length} known contact${r.linked.length !== 1 ? "s" : ""} to these deals` +
|
|
556
|
+
(names.length ? `: ${names.slice(0, 8).join(", ")}${names.length > 8 ? "…" : ""}` : "") + ".");
|
|
557
|
+
}
|
|
558
|
+
lines.push("", "Open accounts have been re-scored. score / get_context now reflect the outcome-graded ICP.");
|
|
559
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
|
|
487
563
|
// ===========================================================================
|
|
488
564
|
// TOOL: query — POST /v2/query
|
|
489
565
|
// Retrieve a corpus of activity across many people. You do the analysis.
|
|
@@ -691,19 +767,6 @@ export function createServer() {
|
|
|
691
767
|
|
|
692
768
|
|
|
693
769
|
|
|
694
|
-
// ===========================================================================
|
|
695
|
-
// A closed deal as the agent may supply it. A bare domain is the original shape and still
|
|
696
|
-
// works; the object form carries the money and the date, which the forecast needs. Widened
|
|
697
|
-
// rather than replaced so nothing that already calls this breaks.
|
|
698
|
-
const CLOSED_DEAL = z.union([
|
|
699
|
-
z.string(),
|
|
700
|
-
z.object({
|
|
701
|
-
domain: z.string().describe("Company domain, e.g. 'acme.com' — no scheme."),
|
|
702
|
-
amount: z.number().optional().describe("Deal value in major units (48000, not 4800000)."),
|
|
703
|
-
currency: z.string().optional().describe("ISO code or symbol. Defaults to the workspace currency."),
|
|
704
|
-
closed_at: z.string().optional().describe("When it actually closed, ISO date. Drives every cycle-time measurement, so pass the real date rather than today's."),
|
|
705
|
-
}),
|
|
706
|
-
]);
|
|
707
770
|
|
|
708
771
|
|
|
709
772
|
|