@opennous/mcp 0.30.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 +1 -1
- package/src/client.js +15 -6
- package/src/server.js +29 -7
package/package.json
CHANGED
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
|
@@ -145,7 +145,8 @@ export function createServer() {
|
|
|
145
145
|
"get_context",
|
|
146
146
|
"Get engineered context for a specific task about a person or company. Pass their email (or " +
|
|
147
147
|
"entity id) and the intent. Returns a focused, ranked context block: the facts that matter for " +
|
|
148
|
-
"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 " +
|
|
149
150
|
"stakeholders, open predictions, and the account's ICP fit score (0-100 + why). Call this before " +
|
|
150
151
|
"drafting outreach, preparing for a meeting, " +
|
|
151
152
|
"or making any decision about a person. A fact's freshness tells you whether to trust it: 'fresh' " +
|
|
@@ -175,8 +176,14 @@ export function createServer() {
|
|
|
175
176
|
lines.push(`ICP FIT: ${ctx.icp.score}/100 — ${label}${ctx.icp.reason ? ` (${ctx.icp.reason})` : ""}`);
|
|
176
177
|
lines.push("");
|
|
177
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
|
+
}
|
|
178
185
|
if (ctx.claims?.length) {
|
|
179
|
-
lines.push(`
|
|
186
|
+
lines.push(`ATTRIBUTES (${ctx.meta?.claims_returned ?? ctx.claims.length}):`);
|
|
180
187
|
for (const c of ctx.claims) {
|
|
181
188
|
lines.push(` ${c.property}: ${fmtVal(c.value)} [${pct(c.confidence)} · ${c.freshness}]`);
|
|
182
189
|
}
|
|
@@ -229,7 +236,8 @@ export function createServer() {
|
|
|
229
236
|
// ===========================================================================
|
|
230
237
|
server.tool(
|
|
231
238
|
"get_account",
|
|
232
|
-
"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 " +
|
|
233
241
|
"confidence and freshness, plus the recent activity timeline. Pass an email or entity UUID. " +
|
|
234
242
|
"For a task-specific, ranked view, prefer get_context.",
|
|
235
243
|
{ id: z.string().describe("Email address or entity UUID") },
|
|
@@ -242,9 +250,15 @@ export function createServer() {
|
|
|
242
250
|
lines.push(`ICP FIT: ${rec.icp.score}/100 — ${label}${rec.icp.reason ? ` (${rec.icp.reason})` : ""}`);
|
|
243
251
|
lines.push("");
|
|
244
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
|
+
}
|
|
245
259
|
const claims = Object.values(rec.claims ?? {});
|
|
246
260
|
if (claims.length) {
|
|
247
|
-
lines.push(`
|
|
261
|
+
lines.push(`ATTRIBUTES (${claims.length}):`);
|
|
248
262
|
for (const c of claims) {
|
|
249
263
|
lines.push(` ${c.property}: ${fmtVal(c.value)} [${pct(c.confidence)} · ${c.freshness}]`);
|
|
250
264
|
}
|
|
@@ -355,7 +369,11 @@ export function createServer() {
|
|
|
355
369
|
" 4. Scheduled meetings/calls are events with property 'interaction.meeting_scheduled' and a " +
|
|
356
370
|
"future-dated `when`. For 'what's booked today/this week', set property:'interaction.meeting_scheduled' " +
|
|
357
371
|
"with from/to bounding the day or week (since_days only looks backward and can't reach them), and " +
|
|
358
|
-
"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.)",
|
|
359
377
|
{
|
|
360
378
|
scope: z.object({
|
|
361
379
|
kind: z.enum(["event", "state"]).optional(),
|
|
@@ -367,6 +385,7 @@ export function createServer() {
|
|
|
367
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"),
|
|
368
386
|
order: z.enum(["asc", "desc"]).optional().describe("observed_at order (default desc, newest first). Use 'asc' for an upcoming-meeting schedule (soonest first)"),
|
|
369
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."),
|
|
370
389
|
}).describe("Corpus filter"),
|
|
371
390
|
without: z.object({
|
|
372
391
|
kind: z.enum(["event", "state"]).optional(),
|
|
@@ -386,7 +405,7 @@ export function createServer() {
|
|
|
386
405
|
const r = await post("/v2/query", body);
|
|
387
406
|
const head = `${r.matched} match${r.matched !== 1 ? "es" : ""}` +
|
|
388
407
|
(r.sampled ? ` (showing ${r.returned})` : "") +
|
|
389
|
-
(r.return === "entities" ? " · grouped by entity" : "");
|
|
408
|
+
(r.corpus === "facts" ? " · facts" : r.return === "entities" ? " · grouped by entity" : "");
|
|
390
409
|
const roll = Object.entries(r.rollups?.by_type ?? {})
|
|
391
410
|
.map(([t, n]) => `${n}× ${fmtType(t)}`).join(" · ");
|
|
392
411
|
const lines = [head, roll].filter(Boolean);
|
|
@@ -395,7 +414,10 @@ export function createServer() {
|
|
|
395
414
|
}
|
|
396
415
|
lines.push("");
|
|
397
416
|
for (const it of r.items ?? []) {
|
|
398
|
-
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") {
|
|
399
421
|
lines.push(` ${it.entity_name ?? it.entity_id} ` +
|
|
400
422
|
`(${it.matches} match${it.matches !== 1 ? "es" : ""}, last ${whenLabel(it.most_recent_type, it.most_recent_at)})` +
|
|
401
423
|
(it.most_recent_value != null ? ` → ${fmtVal(it.most_recent_value)}` : "") +
|