@opennous/mcp 0.37.0 → 0.39.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 +12 -3
- package/src/server.js +87 -56
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennous/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Nous — the Context Graph for AI Agents.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
"nous-mcp": "src/index.js"
|
|
14
14
|
},
|
|
15
15
|
"main": "./src/index.js",
|
|
16
|
-
"files": [
|
|
16
|
+
"files": [
|
|
17
|
+
"src"
|
|
18
|
+
],
|
|
17
19
|
"scripts": {
|
|
18
20
|
"dev": "node --watch src/index.js",
|
|
19
21
|
"start": "node src/index.js",
|
|
@@ -21,7 +23,14 @@
|
|
|
21
23
|
"start:http": "node src/http.js",
|
|
22
24
|
"typecheck": "echo 'no ts in mcp — skipping'"
|
|
23
25
|
},
|
|
24
|
-
"keywords": [
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"nous",
|
|
29
|
+
"memory",
|
|
30
|
+
"crm",
|
|
31
|
+
"gtm",
|
|
32
|
+
"agents"
|
|
33
|
+
],
|
|
25
34
|
"dependencies": {
|
|
26
35
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
27
36
|
"zod": "^3.23.8"
|
package/src/server.js
CHANGED
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
* attention — what needs your attention (accounts gone quiet, facts decayed)
|
|
20
20
|
* verify — re-check a fact before acting on it
|
|
21
21
|
* get_gtm_profile — the user's GTM profile (ICP, market, pricing, product, competitors)
|
|
22
|
-
* update_gtm_profile — write back a change to a GTM context section (evolve, keep history)
|
|
23
22
|
* save_note — attach a note/document (meeting brief, transcript, prep) to a contact
|
|
24
23
|
* search_notes — semantic search over saved notes & documents
|
|
25
24
|
* get_workspace_status — what's set up in this workspace + a ranked next_steps list (call first)
|
|
@@ -44,7 +43,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
44
43
|
import { z } from "zod";
|
|
45
44
|
import { get, post } from "./client.js";
|
|
46
45
|
|
|
47
|
-
export const SERVER_VERSION = "0.
|
|
46
|
+
export const SERVER_VERSION = "0.39.0";
|
|
48
47
|
|
|
49
48
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
50
49
|
|
|
@@ -117,8 +116,8 @@ Nous first even when the user never says "Nous":
|
|
|
117
116
|
- Your action items / what you owe an account -> get_action_items
|
|
118
117
|
- A fact looks stale before you act on it -> verify
|
|
119
118
|
- Our ICP, market, pricing, positioning -> get_gtm_profile
|
|
120
|
-
- Our ICP lives in
|
|
121
|
-
- Our own GTM shifted
|
|
119
|
+
- Our ICP/positioning lives in our own files -> get_icp (file → graph) / get_icp_model (graph → file)
|
|
120
|
+
- Our own GTM shifted (repriced, repositioned) -> edit the context file, then re-run get_icp
|
|
122
121
|
- A brief / note / transcript on a contact -> save_note / search_notes
|
|
123
122
|
- What's set up here and what to do next -> get_workspace_status
|
|
124
123
|
|
|
@@ -629,40 +628,58 @@ export function createServer() {
|
|
|
629
628
|
server.tool("get_gtm_profile", gtmProfileDescription, gtmProfileSchema, gtmProfileHandler);
|
|
630
629
|
|
|
631
630
|
// ===========================================================================
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
// company learns — pair it with get_gtm_profile.
|
|
631
|
+
// TOOLS: get_playbook / sync_playbook — the POLICY layer (vs. facts).
|
|
632
|
+
// Playbooks are versioned rule-docs that GOVERN agent behavior: voice, outreach,
|
|
633
|
+
// icp, positioning. Read the relevant one BEFORE acting; push file edits back so
|
|
634
|
+
// every agent obeys the same rules. GET/POST /v2/playbooks.
|
|
637
635
|
// ===========================================================================
|
|
638
|
-
|
|
639
|
-
"
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
636
|
+
const getPlaybookSchema = {
|
|
637
|
+
kind: z.enum(["voice", "outreach", "icp", "positioning"]).optional()
|
|
638
|
+
.describe("Which policy to read. Omit to list all four."),
|
|
639
|
+
};
|
|
640
|
+
const getPlaybookHandler = async ({ kind }) => {
|
|
641
|
+
const r = await get("/v2/playbooks", kind ? { kind } : undefined);
|
|
642
|
+
const pbs = r.playbooks || [];
|
|
643
|
+
if (!pbs.length) return { content: [{ type: "text", text:
|
|
644
|
+
"No playbooks set up yet. The user can set them up on the Playbooks page or in their context files." }] };
|
|
645
|
+
if (kind) {
|
|
646
|
+
const pb = pbs[0];
|
|
647
|
+
const src = pb.source === "claude_code" ? `mirrors ${pb.file_path}` : "stored in Nous";
|
|
648
|
+
return { content: [{ type: "text", text:
|
|
649
|
+
`# ${pb.title} — ${pb.kind} playbook (v${pb.version}, ${src})\n\n${pb.body_md}` }] };
|
|
650
|
+
}
|
|
651
|
+
const lines = pbs.map(p => ` ${p.kind.padEnd(12)} ${p.title} (${p.source === "claude_code" ? p.file_path : "stored in Nous"})`);
|
|
652
|
+
return { content: [{ type: "text", text:
|
|
653
|
+
"The user's playbooks (read one with get_playbook(kind)):\n" + lines.join("\n") }] };
|
|
654
|
+
};
|
|
655
|
+
server.tool("get_playbook",
|
|
656
|
+
"Read a PLAYBOOK — the user's policy/rules for a kind of action: voice, outreach, icp, or positioning. " +
|
|
657
|
+
"These are RULES TO OBEY, not facts. Read the relevant playbook BEFORE you act: before writing outreach " +
|
|
658
|
+
"read 'voice' and 'outreach'; before scoring or qualifying read 'icp'; for messaging read 'positioning'. " +
|
|
659
|
+
"Omit kind to list all four.",
|
|
660
|
+
getPlaybookSchema, getPlaybookHandler);
|
|
661
|
+
|
|
662
|
+
const syncPlaybookSchema = {
|
|
663
|
+
kind: z.enum(["voice", "outreach", "icp", "positioning"]).describe("Which playbook to update."),
|
|
664
|
+
body_md: z.string().describe("The full markdown content of the playbook."),
|
|
665
|
+
file_path: z.string().optional().describe("The repo file this mirrors, e.g. 'context/icp/icp.md'. Pass it when syncing a Claude Code file so the source is recorded as the file."),
|
|
666
|
+
};
|
|
667
|
+
const syncPlaybookHandler = async ({ kind, body_md, file_path }) => {
|
|
668
|
+
const r = await post(`/v2/playbooks/${kind}`, { body_md, file_path });
|
|
669
|
+
return { content: [{ type: "text", text:
|
|
670
|
+
`Synced the ${r.playbook?.kind || kind} playbook into Nous (v${r.playbook?.version}). Other agents now read the same rules.` }] };
|
|
671
|
+
};
|
|
672
|
+
server.tool("sync_playbook",
|
|
673
|
+
"Push a playbook's content into Nous so the graph stays current. Call this AFTER you edit a policy file " +
|
|
674
|
+
"in the repo (e.g. context/icp/icp.md, references/voice.md), passing the file's new content and its path, " +
|
|
675
|
+
"so Nous mirrors it and every other agent obeys the same rules.",
|
|
676
|
+
syncPlaybookSchema, syncPlaybookHandler);
|
|
677
|
+
|
|
678
|
+
// The GTM context is no longer written through a dedicated MCP tool. In the file
|
|
679
|
+
// symbiosis model the user's own files (context/icp.md, positioning.md, …) are
|
|
680
|
+
// the source of truth: the agent edits those with its own file tools and calls
|
|
681
|
+
// `get_icp` to sync them into the graph (and `get_icp_model` to write the learned
|
|
682
|
+
// model back). The shared POST /v2/workspace/facts route still backs that import.
|
|
666
683
|
|
|
667
684
|
// ===========================================================================
|
|
668
685
|
// TOOL: save_note — POST /v2/notes
|
|
@@ -680,7 +697,7 @@ export function createServer() {
|
|
|
680
697
|
"Notes are append-only and dated, so a contact builds a record across meetings — later you can " +
|
|
681
698
|
"read the last few and see what changed. This is NOT for logging that an interaction happened " +
|
|
682
699
|
"(use `record` with an interaction.* event for that), and NOT for the user's own GTM profile " +
|
|
683
|
-
"(
|
|
700
|
+
"(that lives in their context files — sync it with `get_icp`). Put the full text in `content` — it's kept for agents to read; the " +
|
|
684
701
|
"UI shows the title and date, not the whole body.",
|
|
685
702
|
{
|
|
686
703
|
focus: z.string().describe("Who to attach it to — an email, LinkedIn URL, domain, or entity UUID (not a bare name)."),
|
|
@@ -771,6 +788,10 @@ export function createServer() {
|
|
|
771
788
|
lines.push("SETUP:");
|
|
772
789
|
lines.push(` ${mark(setup.onboarding?.done)} Onboarding${setup.onboarding?.done ? "" : ` — missing ${(setup.onboarding?.missing ?? []).join(", ") || "details"}`}`);
|
|
773
790
|
lines.push(` ${mark(setup.gtm_playbook?.done)} GTM playbook${setup.gtm_playbook?.model ? " (scoring model live)" : ""}${setup.gtm_playbook?.stale_facts ? ` · ${setup.gtm_playbook.stale_facts} stale fact(s)` : ""}`);
|
|
791
|
+
if (setup.icp_sync) {
|
|
792
|
+
const sy = setup.icp_sync;
|
|
793
|
+
lines.push(` ⟳ ICP synced from ${sy.synced_from} (${relAge(sy.synced_at)})${sy.model_changed ? " · model has CHANGED since — run get_icp_model to refresh the file" : ""}`);
|
|
794
|
+
}
|
|
774
795
|
const ints = setup.integrations?.connected ?? [];
|
|
775
796
|
lines.push(` ${mark((setup.integrations?.count ?? 0) > 0)} Integrations (${setup.integrations?.count ?? 0})${ints.length ? `: ${ints.map((i) => i.name).join(", ")}` : ""}`);
|
|
776
797
|
const crm = setup.crm_sync ?? {};
|
|
@@ -822,9 +843,11 @@ export function createServer() {
|
|
|
822
843
|
"IMPORTANT for the ICP: before asking the user to describe their ICP from scratch, if you're in " +
|
|
823
844
|
"Claude Code, look for an ICP they ALREADY wrote — folders like context/, .claude/, gtm/ and files " +
|
|
824
845
|
"named icp*, positioning*, pricing*, competitors*. If you find them, read them and call get_icp to " +
|
|
825
|
-
"sync them (don't retype the ICP here); if none exists,
|
|
826
|
-
"
|
|
827
|
-
"
|
|
846
|
+
"sync them (don't retype the ICP here); if none exists, scaffold a context/ folder (icp.md, " +
|
|
847
|
+
"positioning.md, pricing.md, market.md, competitors.md, gtm-motion.md) from the conversation + your " +
|
|
848
|
+
"site research, then get_icp it — so their ICP lives in their repo. (Not in Claude Code? Capture a " +
|
|
849
|
+
"first cut in the `icp` field here instead.) " +
|
|
850
|
+
"After this, the next step is the context files: call get_icp to sync them into the graph.",
|
|
828
851
|
{
|
|
829
852
|
name: z.string().optional().describe("The user's company / workspace name."),
|
|
830
853
|
website: z.string().optional().describe("The company website (used to seed the GTM context)."),
|
|
@@ -848,25 +871,26 @@ export function createServer() {
|
|
|
848
871
|
].filter(Boolean);
|
|
849
872
|
return { content: [{ type: "text", text:
|
|
850
873
|
`Workspace profile saved.${set.length ? ` ${set.join(" · ")}.` : ""}\n` +
|
|
851
|
-
`Next: call get_workspace_status to see what to set up next (usually the
|
|
874
|
+
`Next: call get_workspace_status to see what to set up next (usually syncing the ICP/context files with get_icp).` }] };
|
|
852
875
|
}
|
|
853
876
|
);
|
|
854
877
|
|
|
855
878
|
// ===========================================================================
|
|
856
879
|
// TOOL: build_scoring_model — POST /v2/workspace/scoring-model
|
|
857
|
-
// The second half of building the GTM playbook. The agent
|
|
858
|
-
//
|
|
880
|
+
// The second half of building the GTM playbook. The agent syncs the GTM context
|
|
881
|
+
// from the user's files with get_icp, then calls this to turn it into a weighted
|
|
859
882
|
// ICP scoring model. After this, accounts get scored for fit and
|
|
860
883
|
// get_workspace_status shows the playbook as done.
|
|
861
884
|
// ===========================================================================
|
|
862
885
|
server.tool(
|
|
863
886
|
"build_scoring_model",
|
|
864
|
-
"Build (or rebuild) the user's ICP scoring model from
|
|
865
|
-
"the second half of setting up the GTM playbook: first
|
|
866
|
-
"
|
|
867
|
-
"signals so accounts get scored for fit.
|
|
868
|
-
"
|
|
869
|
-
"
|
|
887
|
+
"Build (or rebuild) the user's ICP scoring model from their synced GTM context. This is " +
|
|
888
|
+
"the second half of setting up the GTM playbook: first sync the user's ICP/positioning/pricing " +
|
|
889
|
+
"files with get_icp, then call this to translate that context into a weighted set of scoring " +
|
|
890
|
+
"signals so accounts get scored for fit. (get_icp usually builds the model on first sync, so you " +
|
|
891
|
+
"often won't need this directly.) If a model already exists it is left alone unless you " +
|
|
892
|
+
"pass force:true (use that when the context files have changed and the model should be rebuilt). If " +
|
|
893
|
+
"it reports no GTM context yet, sync the user's context files with get_icp first, then call this again. " +
|
|
870
894
|
"STRONGER than this tool: if the user can name a few closed-WON and closed-LOST customer domains, " +
|
|
871
895
|
"call record_closed_deals instead (or as well) — it trains the model on real outcomes via " +
|
|
872
896
|
"contrastive lift, which beats a model inferred from a description.",
|
|
@@ -888,7 +912,7 @@ export function createServer() {
|
|
|
888
912
|
const msg = String(e?.message ?? e);
|
|
889
913
|
if (msg.includes("no_gtm_context")) {
|
|
890
914
|
return { content: [{ type: "text", text:
|
|
891
|
-
"No GTM context
|
|
915
|
+
"No GTM context yet. Sync the user's ICP/context files with get_icp first (or scaffold context/icp.md, then get_icp), then build the model." }] };
|
|
892
916
|
}
|
|
893
917
|
if (msg.includes("model_exists")) {
|
|
894
918
|
return { content: [{ type: "text", text:
|
|
@@ -956,12 +980,19 @@ export function createServer() {
|
|
|
956
980
|
"in the project for an existing GTM setup — folders like context/, .claude/, gtm/, and files named " +
|
|
957
981
|
"icp*, positioning*, pricing*, competitors*, messaging*, market*. READ the ones you find with your " +
|
|
958
982
|
"own file tools, then call this with each file's content mapped to a section, AND its path in " +
|
|
959
|
-
"`source_path`.
|
|
983
|
+
"`source_path`. MAP GRANULARLY: map each FILE to the single section it best fits (icp.md -> ICP, " +
|
|
984
|
+
"positioning.md -> Positioning, pricing.md -> Pricing, competitors.md -> Competitors, market.md -> " +
|
|
985
|
+
"Market, messaging.md -> Notes) — one entry per file, do NOT dump several files' content into ICP. " +
|
|
986
|
+
"If one file holds several sections under headers, split it by header into multiple entries. " +
|
|
987
|
+
"Nous keeps a served copy of the prose and rebuilds the ICP scoring model from it; " +
|
|
960
988
|
"the recorded source_path is what get_icp_model writes the learned model back into. " +
|
|
961
|
-
"IF NO ICP
|
|
962
|
-
"
|
|
963
|
-
"
|
|
964
|
-
"
|
|
989
|
+
"IF NO ICP FILES EXIST: don't invent context in Nous. Offer to SCAFFOLD a context/ folder in their " +
|
|
990
|
+
"repo — context/icp.md, positioning.md, pricing.md, market.md, competitors.md, gtm-motion.md — " +
|
|
991
|
+
"filled from what the user tells you plus your own research of their website (write them with your " +
|
|
992
|
+
"file tools), then call this on those files — so their GTM context lives in their repo where they'll " +
|
|
993
|
+
"keep editing it. At minimum create context/icp.md if that's all they'll give you. Re-run this after " +
|
|
994
|
+
"the user edits any context file to re-sync. The ICP section's source_path matters most (it's the " +
|
|
995
|
+
"write-back target for get_icp_model).",
|
|
965
996
|
{
|
|
966
997
|
sections: z.array(z.object({
|
|
967
998
|
section: z.enum(["ICP", "Market", "Product", "Pricing", "Competitors", "Positioning", "GTM Motion", "Notes"])
|