@opennous/mcp 0.38.0 → 0.40.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 +82 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennous/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.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. Follow the Nous document house style so every playbook reads like a clean text file: a '# Title' line, a '> ' one-paragraph lede, an optional plain 'Key: value' block, a '---' divider, then '## Title-case' sections with plain '- ' bullets. Keep it markdown, no decorative formatting."),
|
|
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)."),
|
|
@@ -826,9 +843,11 @@ export function createServer() {
|
|
|
826
843
|
"IMPORTANT for the ICP: before asking the user to describe their ICP from scratch, if you're in " +
|
|
827
844
|
"Claude Code, look for an ICP they ALREADY wrote — folders like context/, .claude/, gtm/ and files " +
|
|
828
845
|
"named icp*, positioning*, pricing*, competitors*. If you find them, read them and call get_icp to " +
|
|
829
|
-
"sync them (don't retype the ICP here); if none exists,
|
|
830
|
-
"
|
|
831
|
-
"
|
|
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.",
|
|
832
851
|
{
|
|
833
852
|
name: z.string().optional().describe("The user's company / workspace name."),
|
|
834
853
|
website: z.string().optional().describe("The company website (used to seed the GTM context)."),
|
|
@@ -852,25 +871,26 @@ export function createServer() {
|
|
|
852
871
|
].filter(Boolean);
|
|
853
872
|
return { content: [{ type: "text", text:
|
|
854
873
|
`Workspace profile saved.${set.length ? ` ${set.join(" · ")}.` : ""}\n` +
|
|
855
|
-
`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).` }] };
|
|
856
875
|
}
|
|
857
876
|
);
|
|
858
877
|
|
|
859
878
|
// ===========================================================================
|
|
860
879
|
// TOOL: build_scoring_model — POST /v2/workspace/scoring-model
|
|
861
|
-
// The second half of building the GTM playbook. The agent
|
|
862
|
-
//
|
|
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
|
|
863
882
|
// ICP scoring model. After this, accounts get scored for fit and
|
|
864
883
|
// get_workspace_status shows the playbook as done.
|
|
865
884
|
// ===========================================================================
|
|
866
885
|
server.tool(
|
|
867
886
|
"build_scoring_model",
|
|
868
|
-
"Build (or rebuild) the user's ICP scoring model from
|
|
869
|
-
"the second half of setting up the GTM playbook: first
|
|
870
|
-
"
|
|
871
|
-
"signals so accounts get scored for fit.
|
|
872
|
-
"
|
|
873
|
-
"
|
|
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. " +
|
|
874
894
|
"STRONGER than this tool: if the user can name a few closed-WON and closed-LOST customer domains, " +
|
|
875
895
|
"call record_closed_deals instead (or as well) — it trains the model on real outcomes via " +
|
|
876
896
|
"contrastive lift, which beats a model inferred from a description.",
|
|
@@ -892,7 +912,7 @@ export function createServer() {
|
|
|
892
912
|
const msg = String(e?.message ?? e);
|
|
893
913
|
if (msg.includes("no_gtm_context")) {
|
|
894
914
|
return { content: [{ type: "text", text:
|
|
895
|
-
"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." }] };
|
|
896
916
|
}
|
|
897
917
|
if (msg.includes("model_exists")) {
|
|
898
918
|
return { content: [{ type: "text", text:
|
|
@@ -966,10 +986,17 @@ export function createServer() {
|
|
|
966
986
|
"If one file holds several sections under headers, split it by header into multiple entries. " +
|
|
967
987
|
"Nous keeps a served copy of the prose and rebuilds the ICP scoring model from it; " +
|
|
968
988
|
"the recorded source_path is what get_icp_model writes the learned model back into. " +
|
|
969
|
-
"
|
|
970
|
-
"
|
|
971
|
-
"
|
|
972
|
-
"
|
|
989
|
+
"INCLUDE EXCLUSIONS: if the ICP names who they will NOT work with (e.g. 'not cold-calling " +
|
|
990
|
+
"agencies', 'no pure branding/messaging shops'), keep that text IN the ICP section — Nous turns " +
|
|
991
|
+
"each stated exclusion into a hard disqualifier that caps those accounts below Not-ICP, even when " +
|
|
992
|
+
"they also match the firmographics. So a 'Not a fit' list in icp.md actively lowers their score. " +
|
|
993
|
+
"IF NO ICP FILES EXIST: don't invent context in Nous. Offer to SCAFFOLD a context/ folder in their " +
|
|
994
|
+
"repo — context/icp.md, positioning.md, pricing.md, market.md, competitors.md, gtm-motion.md — " +
|
|
995
|
+
"filled from what the user tells you plus your own research of their website (write them with your " +
|
|
996
|
+
"file tools), then call this on those files — so their GTM context lives in their repo where they'll " +
|
|
997
|
+
"keep editing it. At minimum create context/icp.md if that's all they'll give you. Re-run this after " +
|
|
998
|
+
"the user edits any context file to re-sync. The ICP section's source_path matters most (it's the " +
|
|
999
|
+
"write-back target for get_icp_model).",
|
|
973
1000
|
{
|
|
974
1001
|
sections: z.array(z.object({
|
|
975
1002
|
section: z.enum(["ICP", "Market", "Product", "Pricing", "Competitors", "Positioning", "GTM Motion", "Notes"])
|