@opennous/mcp 0.29.0 → 0.30.1
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 +2 -2
- package/src/client.js +15 -6
- package/src/server.js +77 -9
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opennous/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Nous
|
|
3
|
+
"version": "0.30.1",
|
|
4
|
+
"description": "Nous — the Context Graph for AI Agents.",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
package/src/client.js
CHANGED
|
@@ -106,13 +106,22 @@ async function request(method, path, { body, query } = {}) {
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
if (!res.ok) {
|
|
109
|
-
let
|
|
110
|
-
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
let err = {};
|
|
110
|
+
try { err = await res.json(); } catch { /* non-JSON body */ }
|
|
111
|
+
// Cloud-only feature on a self-hosted workspace: turn the raw 403 into a clear,
|
|
112
|
+
// non-alarming message so the agent stops reaching for it and explains why.
|
|
113
|
+
if (res.status === 403 && err.error === "cloud_only_feature") {
|
|
114
|
+
const feat = err.feature === "crmSync" ? "CRM sync"
|
|
115
|
+
: err.feature === "leadLists" ? "lead lists"
|
|
116
|
+
: err.feature || "this";
|
|
117
|
+
throw new Error(
|
|
118
|
+
`This is a Nous Cloud feature (${feat}) and isn't available on a self-hosted ` +
|
|
119
|
+
`workspace. CRM sync and lead lists are the cloud-only team layer — everything ` +
|
|
120
|
+
`else (context, accounts, ICP scoring, notes, integrations) works on self-host. ` +
|
|
121
|
+
`Don't retry; tell the user this needs Nous Cloud.`
|
|
122
|
+
);
|
|
115
123
|
}
|
|
124
|
+
const errorMessage = err.error || err.message || res.statusText;
|
|
116
125
|
throw new Error(`Nous API error (${res.status}): ${errorMessage}`);
|
|
117
126
|
}
|
|
118
127
|
|
package/src/server.js
CHANGED
|
@@ -41,7 +41,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
41
41
|
import { z } from "zod";
|
|
42
42
|
import { get, post } from "./client.js";
|
|
43
43
|
|
|
44
|
-
export const SERVER_VERSION = "0.
|
|
44
|
+
export const SERVER_VERSION = "0.35.0";
|
|
45
45
|
|
|
46
46
|
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
47
47
|
|
|
@@ -111,6 +111,7 @@ Nous first even when the user never says "Nous":
|
|
|
111
111
|
- Something happened or you learned a fact -> record
|
|
112
112
|
- Activity or a list across many accounts -> query
|
|
113
113
|
- What needs attention, what has gone quiet -> attention
|
|
114
|
+
- Your action items / what you owe an account -> get_action_items
|
|
114
115
|
- A fact looks stale before you act on it -> verify
|
|
115
116
|
- Our ICP, market, pricing, positioning -> get_gtm_profile
|
|
116
117
|
- Our own GTM shifted -> update_gtm_profile
|
|
@@ -127,7 +128,7 @@ export function createServer() {
|
|
|
127
128
|
name: "nous",
|
|
128
129
|
version: SERVER_VERSION,
|
|
129
130
|
description:
|
|
130
|
-
"Nous — the
|
|
131
|
+
"Nous — the Context Graph for AI Agents. Nous is operated by the agent, not by a human " +
|
|
131
132
|
"clicking around: call get_workspace_status at the start of a session to see what's set up " +
|
|
132
133
|
"and what to set up next. Call get_context before drafting outreach or preparing for a " +
|
|
133
134
|
"meeting. Call record after every interaction, or whenever you learn something.",
|
|
@@ -144,7 +145,8 @@ export function createServer() {
|
|
|
144
145
|
"get_context",
|
|
145
146
|
"Get engineered context for a specific task about a person or company. Pass their email (or " +
|
|
146
147
|
"entity id) and the intent. Returns a focused, ranked context block: the facts that matter for " +
|
|
147
|
-
"that task — each with a confidence and a freshness —
|
|
148
|
+
"that task — each with a confidence and a freshness — the durable FACTS we've learned about them " +
|
|
149
|
+
"(their atomic memory: budget, authority, pain, stack, plans), the recent timeline, the buying-group " +
|
|
148
150
|
"stakeholders, open predictions, and the account's ICP fit score (0-100 + why). Call this before " +
|
|
149
151
|
"drafting outreach, preparing for a meeting, " +
|
|
150
152
|
"or making any decision about a person. A fact's freshness tells you whether to trust it: 'fresh' " +
|
|
@@ -174,8 +176,14 @@ export function createServer() {
|
|
|
174
176
|
lines.push(`ICP FIT: ${ctx.icp.score}/100 — ${label}${ctx.icp.reason ? ` (${ctx.icp.reason})` : ""}`);
|
|
175
177
|
lines.push("");
|
|
176
178
|
}
|
|
179
|
+
if (ctx.facts?.length) {
|
|
180
|
+
// Atomic memory — the durable, decision-relevant facts learned about them.
|
|
181
|
+
lines.push(`FACTS (${ctx.facts.length} — durable memory about them):`);
|
|
182
|
+
for (const f of ctx.facts) lines.push(` [${f.category}] ${f.content}${f.date ? ` (${relAge(f.date)})` : ""}`);
|
|
183
|
+
lines.push("");
|
|
184
|
+
}
|
|
177
185
|
if (ctx.claims?.length) {
|
|
178
|
-
lines.push(`
|
|
186
|
+
lines.push(`ATTRIBUTES (${ctx.meta?.claims_returned ?? ctx.claims.length}):`);
|
|
179
187
|
for (const c of ctx.claims) {
|
|
180
188
|
lines.push(` ${c.property}: ${fmtVal(c.value)} [${pct(c.confidence)} · ${c.freshness}]`);
|
|
181
189
|
}
|
|
@@ -228,7 +236,8 @@ export function createServer() {
|
|
|
228
236
|
// ===========================================================================
|
|
229
237
|
server.tool(
|
|
230
238
|
"get_account",
|
|
231
|
-
"Get the full account record for a person or company —
|
|
239
|
+
"Get the full account record for a person or company — the durable FACTS we've learned about them " +
|
|
240
|
+
"(their atomic memory: budget, authority, pain, stack, plans), every attribute (claim) with its " +
|
|
232
241
|
"confidence and freshness, plus the recent activity timeline. Pass an email or entity UUID. " +
|
|
233
242
|
"For a task-specific, ranked view, prefer get_context.",
|
|
234
243
|
{ id: z.string().describe("Email address or entity UUID") },
|
|
@@ -241,9 +250,15 @@ export function createServer() {
|
|
|
241
250
|
lines.push(`ICP FIT: ${rec.icp.score}/100 — ${label}${rec.icp.reason ? ` (${rec.icp.reason})` : ""}`);
|
|
242
251
|
lines.push("");
|
|
243
252
|
}
|
|
253
|
+
if (rec.facts?.length) {
|
|
254
|
+
// Atomic memory — the durable, decision-relevant facts learned about them.
|
|
255
|
+
lines.push(`FACTS (${rec.facts.length} — durable memory about them):`);
|
|
256
|
+
for (const f of rec.facts) lines.push(` [${f.category}] ${f.content}${f.date ? ` (${relAge(f.date)})` : ""}`);
|
|
257
|
+
lines.push("");
|
|
258
|
+
}
|
|
244
259
|
const claims = Object.values(rec.claims ?? {});
|
|
245
260
|
if (claims.length) {
|
|
246
|
-
lines.push(`
|
|
261
|
+
lines.push(`ATTRIBUTES (${claims.length}):`);
|
|
247
262
|
for (const c of claims) {
|
|
248
263
|
lines.push(` ${c.property}: ${fmtVal(c.value)} [${pct(c.confidence)} · ${c.freshness}]`);
|
|
249
264
|
}
|
|
@@ -354,7 +369,11 @@ export function createServer() {
|
|
|
354
369
|
" 4. Scheduled meetings/calls are events with property 'interaction.meeting_scheduled' and a " +
|
|
355
370
|
"future-dated `when`. For 'what's booked today/this week', set property:'interaction.meeting_scheduled' " +
|
|
356
371
|
"with from/to bounding the day or week (since_days only looks backward and can't reach them), and " +
|
|
357
|
-
"order:'asc' to list soonest-first. Meeting rows render the absolute date and time
|
|
372
|
+
"order:'asc' to list soonest-first. Meeting rows render the absolute date and time.\n" +
|
|
373
|
+
" 5. scope.facts:true + question searches the FACTS corpus (durable atomic facts about accounts) " +
|
|
374
|
+
"instead of activity — cross-account semantic fact search like 'which accounts want off Clay' or " +
|
|
375
|
+
"'who is hiring'. return:'entities' gives the single best-matching fact per account. (A single " +
|
|
376
|
+
"account's facts already come back inline with get_account.)",
|
|
358
377
|
{
|
|
359
378
|
scope: z.object({
|
|
360
379
|
kind: z.enum(["event", "state"]).optional(),
|
|
@@ -366,6 +385,7 @@ export function createServer() {
|
|
|
366
385
|
to: z.string().optional().describe("ISO timestamp — only activity at/before this (absolute upper bound). Combine from+to for a window; future-dated for upcoming meetings"),
|
|
367
386
|
order: z.enum(["asc", "desc"]).optional().describe("observed_at order (default desc, newest first). Use 'asc' for an upcoming-meeting schedule (soonest first)"),
|
|
368
387
|
limit: z.number().optional().describe("max items (default 50, cap 200)"),
|
|
388
|
+
facts: z.boolean().optional().describe("search the FACTS corpus (durable atomic facts about accounts) instead of activity. Needs `question` — a cross-account semantic fact search, e.g. 'which accounts want off Clay'. return:'entities' = the best matching fact per account."),
|
|
369
389
|
}).describe("Corpus filter"),
|
|
370
390
|
without: z.object({
|
|
371
391
|
kind: z.enum(["event", "state"]).optional(),
|
|
@@ -385,7 +405,7 @@ export function createServer() {
|
|
|
385
405
|
const r = await post("/v2/query", body);
|
|
386
406
|
const head = `${r.matched} match${r.matched !== 1 ? "es" : ""}` +
|
|
387
407
|
(r.sampled ? ` (showing ${r.returned})` : "") +
|
|
388
|
-
(r.return === "entities" ? " · grouped by entity" : "");
|
|
408
|
+
(r.corpus === "facts" ? " · facts" : r.return === "entities" ? " · grouped by entity" : "");
|
|
389
409
|
const roll = Object.entries(r.rollups?.by_type ?? {})
|
|
390
410
|
.map(([t, n]) => `${n}× ${fmtType(t)}`).join(" · ");
|
|
391
411
|
const lines = [head, roll].filter(Boolean);
|
|
@@ -394,7 +414,10 @@ export function createServer() {
|
|
|
394
414
|
}
|
|
395
415
|
lines.push("");
|
|
396
416
|
for (const it of r.items ?? []) {
|
|
397
|
-
if (r.
|
|
417
|
+
if (r.corpus === "facts") {
|
|
418
|
+
lines.push(` ${it.entity_name ?? it.entity_id} [${it.category}] ${it.content}` +
|
|
419
|
+
(it.similarity != null ? ` (${it.similarity})` : ""));
|
|
420
|
+
} else if (r.return === "entities") {
|
|
398
421
|
lines.push(` ${it.entity_name ?? it.entity_id} ` +
|
|
399
422
|
`(${it.matches} match${it.matches !== 1 ? "es" : ""}, last ${whenLabel(it.most_recent_type, it.most_recent_at)})` +
|
|
400
423
|
(it.most_recent_value != null ? ` → ${fmtVal(it.most_recent_value)}` : "") +
|
|
@@ -437,6 +460,51 @@ export function createServer() {
|
|
|
437
460
|
}
|
|
438
461
|
);
|
|
439
462
|
|
|
463
|
+
// ===========================================================================
|
|
464
|
+
// TOOL: get_action_items — GET /v2/action-items
|
|
465
|
+
// Commitments extracted from meetings/emails — what you owe each account.
|
|
466
|
+
// ===========================================================================
|
|
467
|
+
server.tool(
|
|
468
|
+
"get_action_items",
|
|
469
|
+
"Your open action items and commitments, pulled from meeting notes and emails — what you owe " +
|
|
470
|
+
"which account (and what they owe you), so you don't have to dig through transcripts. Use for " +
|
|
471
|
+
"'what are my action items', 'what do I owe <account>', 'what's outstanding this week'. Defaults " +
|
|
472
|
+
"to YOUR open items across all accounts, grouped by account.",
|
|
473
|
+
{
|
|
474
|
+
owner: z.enum(["me", "prospect", "all"]).optional().describe("Whose commitments — me (default), the prospect, or all"),
|
|
475
|
+
status: z.enum(["open", "done", "all"]).optional().describe("open (default), done, or all"),
|
|
476
|
+
focus: z.string().optional().describe("Scope to one account — an email or entity UUID"),
|
|
477
|
+
due: z.enum(["today", "week", "all"]).optional().describe("Only items due today / this week (items that carry a due date) — default all"),
|
|
478
|
+
},
|
|
479
|
+
async ({ owner, status, focus, due }) => {
|
|
480
|
+
const params = {};
|
|
481
|
+
if (owner) params.owner = owner;
|
|
482
|
+
if (status) params.status = status;
|
|
483
|
+
if (focus) params.focus = focus;
|
|
484
|
+
if (due) params.due = due;
|
|
485
|
+
const r = await get("/v2/action-items", params);
|
|
486
|
+
const items = r.items ?? [];
|
|
487
|
+
if (!items.length) return { content: [{ type: "text", text: "No matching action items." }] };
|
|
488
|
+
|
|
489
|
+
const byAccount = new Map();
|
|
490
|
+
for (const it of items) {
|
|
491
|
+
const key = it.account || it.account_email || it.entity_id || "—";
|
|
492
|
+
if (!byAccount.has(key)) byAccount.set(key, []);
|
|
493
|
+
byAccount.get(key).push(it);
|
|
494
|
+
}
|
|
495
|
+
const lines = [`${items.length} action item${items.length !== 1 ? "s" : ""}:`];
|
|
496
|
+
for (const [account, list] of byAccount) {
|
|
497
|
+
lines.push(`\n${account}:`);
|
|
498
|
+
for (const it of list) {
|
|
499
|
+
const who = it.owner_kind === "prospect" ? "[them]" : "[you]";
|
|
500
|
+
const when = it.due_at ? ` (due ${fmtWhen(it.due_at)})` : "";
|
|
501
|
+
lines.push(` ${who} ${it.title}${when}`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
|
|
440
508
|
// ===========================================================================
|
|
441
509
|
// TOOL: verify — POST /v2/verify
|
|
442
510
|
// Re-check a fact before acting on it — the calibration check.
|