frinus-mcp 3.6.4 → 3.8.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/README.md +1 -1
- package/dist/client/cp-client.d.ts +14 -1
- package/dist/client/cp-client.js +36 -0
- package/dist/client/memory-client.d.ts +13 -0
- package/dist/client/memory-client.js +21 -0
- package/dist/index.js +133 -19
- package/dist/notifications.d.ts +44 -0
- package/dist/notifications.js +134 -0
- package/dist/tools/boot-state.d.ts +56 -0
- package/dist/tools/boot-state.js +115 -0
- package/dist/tools/definitions.js +168 -7
- package/dist/tools/handlers.js +302 -19
- package/dist/types/index.d.ts +48 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +9 -0
- package/manifest.json +2 -2
- package/package.json +1 -1
package/dist/tools/handlers.js
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
import axios from "axios";
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
9
|
import { getResolvedTenantOrgId } from "../client/memory-client.js";
|
|
10
|
+
import { fetchNotificationsBlock } from "../notifications.js";
|
|
11
|
+
import { MCP_VERSION } from "../version.js";
|
|
12
|
+
import { getSessionId, markBooted, resetReorientCounter } from "./boot-state.js";
|
|
10
13
|
// ---------------------------------------------------------------------------
|
|
11
14
|
// Credential delivery — server-side broker, never by value
|
|
12
15
|
// ---------------------------------------------------------------------------
|
|
@@ -329,6 +332,12 @@ function renderStoreResult(result) {
|
|
|
329
332
|
const id = result?.id;
|
|
330
333
|
const status = result?.classification_status;
|
|
331
334
|
const head = `✓ Salva`;
|
|
335
|
+
// Non-fatal note when a supplied context_path did not fully resolve by name
|
|
336
|
+
// (ambiguous or missing segment). The memory WAS stored; this is appended to
|
|
337
|
+
// whatever destination line we render below so the caller can fix the path.
|
|
338
|
+
const warning = typeof result?.context_path_warning === "string" && result.context_path_warning
|
|
339
|
+
? `\n ⚠ context_path: ${result.context_path_warning}`
|
|
340
|
+
: "";
|
|
332
341
|
const applied = appliedLabel(result);
|
|
333
342
|
const pending = status === "proposed" || status === "universe_only";
|
|
334
343
|
// Anchored: a real destination exists. Report it, and only then mention a
|
|
@@ -341,7 +350,7 @@ function renderStoreResult(result) {
|
|
|
341
350
|
out += `\n ↳ sugestão pendente (NÃO aplicada): ${sug} — revise com memory_classification_pending`;
|
|
342
351
|
}
|
|
343
352
|
}
|
|
344
|
-
return out;
|
|
353
|
+
return out + warning;
|
|
345
354
|
}
|
|
346
355
|
// Not anchored anywhere: the suggestion is all we have to offer.
|
|
347
356
|
if (pending) {
|
|
@@ -349,12 +358,12 @@ function renderStoreResult(result) {
|
|
|
349
358
|
const conf = confidenceLabel(result?.classification_confidence);
|
|
350
359
|
if (sug) {
|
|
351
360
|
const confTxt = conf ? `, confiança ${conf}` : "";
|
|
352
|
-
return `${head} · sem destino aplicado · sugestão: ${sug} (auto${confTxt}) — aprove com memory_classification_approve
|
|
361
|
+
return `${head} · sem destino aplicado · sugestão: ${sug} (auto${confTxt}) — aprove com memory_classification_approve` + warning;
|
|
353
362
|
}
|
|
354
|
-
return `${head} · sem destino aplicado e sem sugestão confiável — use memory_classification_pending para revisar
|
|
363
|
+
return `${head} · sem destino aplicado e sem sugestão confiável — use memory_classification_pending para revisar` + warning;
|
|
355
364
|
}
|
|
356
365
|
// Defensive: older backend without classification fields, or no hierarchy at all.
|
|
357
|
-
return `${head}\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}
|
|
366
|
+
return `${head}\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}` + warning;
|
|
358
367
|
}
|
|
359
368
|
const handlers = {
|
|
360
369
|
// ==========================================================================
|
|
@@ -386,6 +395,7 @@ const handlers = {
|
|
|
386
395
|
created_by_user_id: createdByUser,
|
|
387
396
|
metadata: args.metadata,
|
|
388
397
|
context_id: args.context_id,
|
|
398
|
+
context_path: args.context_path,
|
|
389
399
|
credential_ref: args.credential_ref,
|
|
390
400
|
universe_id: universeId,
|
|
391
401
|
scope: args.scope,
|
|
@@ -399,6 +409,43 @@ const handlers = {
|
|
|
399
409
|
throw error;
|
|
400
410
|
}
|
|
401
411
|
},
|
|
412
|
+
async hierarchy_find(args, { memoryClient }) {
|
|
413
|
+
let result;
|
|
414
|
+
try {
|
|
415
|
+
result = await memoryClient.findHierarchy({
|
|
416
|
+
query_text: args.query_text,
|
|
417
|
+
universe_id: args.universe_id,
|
|
418
|
+
limit: args.limit,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
const apiErr = handleApiError(error);
|
|
423
|
+
if (apiErr)
|
|
424
|
+
return apiErr;
|
|
425
|
+
throw error;
|
|
426
|
+
}
|
|
427
|
+
const candidates = Array.isArray(result?.candidates) ? result.candidates : [];
|
|
428
|
+
if (candidates.length === 0) {
|
|
429
|
+
return {
|
|
430
|
+
content: [{
|
|
431
|
+
type: "text",
|
|
432
|
+
text: "Nenhum nó candidato para esta consulta. Salve mesmo assim (o backend classifica ou cai no universo/Geral), ou refine a consulta.",
|
|
433
|
+
}],
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
let formatted = "Nós candidatos (copie o caminho POR NOME para memory_store(context_path=...)):\n\n";
|
|
437
|
+
candidates.forEach((c, i) => {
|
|
438
|
+
const path = typeof c?.context_path === "string" ? c.context_path : "(sem caminho)";
|
|
439
|
+
const conf = confidenceLabel(c?.confidence);
|
|
440
|
+
const confTxt = typeof c?.confidence === "number"
|
|
441
|
+
? ` (confiança ${c.confidence.toFixed(2)}${conf ? ` · ${conf}` : ""})`
|
|
442
|
+
: "";
|
|
443
|
+
const level = c?.level ? ` [${c.level}]` : "";
|
|
444
|
+
formatted += `${i + 1}. ${path}${level}${confTxt}\n`;
|
|
445
|
+
});
|
|
446
|
+
formatted += "\nCopie o caminho (os nomes), não o context_id. Confiança ≥ 0,60 ancora sozinho; abaixo disso vai para a fila de revisão.";
|
|
447
|
+
return { content: [{ type: "text", text: formatted }] };
|
|
448
|
+
},
|
|
402
449
|
// ==========================================================================
|
|
403
450
|
// Auto-classification (human-in-the-loop)
|
|
404
451
|
// ==========================================================================
|
|
@@ -982,7 +1029,7 @@ const handlers = {
|
|
|
982
1029
|
// ==========================================================================
|
|
983
1030
|
// Maintenance
|
|
984
1031
|
// ==========================================================================
|
|
985
|
-
async heartbeat_tick(args, { memoryClient }) {
|
|
1032
|
+
async heartbeat_tick(args, { memoryClient, cpClient }) {
|
|
986
1033
|
const result = await memoryClient.heartbeatTick({
|
|
987
1034
|
agent_id: args.agent_id,
|
|
988
1035
|
context_id: args.context_id,
|
|
@@ -1012,6 +1059,13 @@ const handlers = {
|
|
|
1012
1059
|
formatted += ` ${action}: at interaction ${count}\n`;
|
|
1013
1060
|
}
|
|
1014
1061
|
}
|
|
1062
|
+
// Piggyback the notifications check on the heartbeat. fetchNotificationsBlock
|
|
1063
|
+
// self-throttles (at most once per FRINUS_NOTIFICATIONS_INTERVAL_MIN) and
|
|
1064
|
+
// dedupes against what this session already showed, so this is cheap and
|
|
1065
|
+
// never shows the same item twice.
|
|
1066
|
+
const notif = await fetchNotificationsBlock(cpClient, { force: false });
|
|
1067
|
+
if (notif)
|
|
1068
|
+
formatted += `\n${notif}`;
|
|
1015
1069
|
return { content: [{ type: "text", text: formatted }] };
|
|
1016
1070
|
},
|
|
1017
1071
|
async sleep_run(args, { memoryClient }) {
|
|
@@ -1246,25 +1300,79 @@ const handlers = {
|
|
|
1246
1300
|
}],
|
|
1247
1301
|
};
|
|
1248
1302
|
},
|
|
1249
|
-
async agent_bootstrap(args, { agentClient }) {
|
|
1303
|
+
async agent_bootstrap(args, { agentClient, cpClient, memoryClient, capture, resolvedUserEmail, resolvedUserId }) {
|
|
1304
|
+
// Kick off the notifications fetch concurrently with the bootstrap call so
|
|
1305
|
+
// it adds no latency on the critical path. fetchNotificationsBlock never
|
|
1306
|
+
// throws and self-limits to a 4s timeout, so bootstrap can neither fail nor
|
|
1307
|
+
// stall because of it; the extra .catch is defence in depth.
|
|
1308
|
+
const notifPromise = fetchNotificationsBlock(cpClient, { force: true }).catch(() => "");
|
|
1250
1309
|
try {
|
|
1251
1310
|
const result = await agentClient.bootstrapAgent(args.agent_id);
|
|
1252
1311
|
const identity = result.identity || {};
|
|
1253
1312
|
const runtime = result.runtime || {};
|
|
1313
|
+
const bootConfig = result.config || {};
|
|
1314
|
+
const agentIdStr = String(identity.id || args.agent_id || "");
|
|
1315
|
+
// --- Session lifecycle: the MCP owns it ------------------------------
|
|
1316
|
+
// The boot protocol must not depend on the model remembering to call
|
|
1317
|
+
// session_start / session_end. We start the formal session HERE (once per
|
|
1318
|
+
// process) and record it in the boot state; the process-exit hooks in
|
|
1319
|
+
// index.ts close it (session_summary + session_end). Best-effort: a
|
|
1320
|
+
// Memory Engine hiccup must never make bootstrap fail.
|
|
1321
|
+
let activeSessionId = getSessionId();
|
|
1322
|
+
if (!activeSessionId) {
|
|
1323
|
+
try {
|
|
1324
|
+
const s = await memoryClient.startSession({ agent_id: agentIdStr });
|
|
1325
|
+
activeSessionId = s?.session_id || null;
|
|
1326
|
+
if (activeSessionId)
|
|
1327
|
+
capture.sessionId = activeSessionId;
|
|
1328
|
+
}
|
|
1329
|
+
catch (err) {
|
|
1330
|
+
console.error("[MCP] bootstrap session_start failed:", err?.message || err);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
// Arm the deterministic reorient cadence from the authoritative config.
|
|
1334
|
+
const reorientEveryN = Number(bootConfig.reorient_every_n) || 10;
|
|
1335
|
+
const reorientPct = Number(bootConfig.reorient_on_context_pct) || 70;
|
|
1336
|
+
markBooted({
|
|
1337
|
+
agentId: agentIdStr,
|
|
1338
|
+
sessionId: activeSessionId,
|
|
1339
|
+
config: { reorientEveryN, reorientOnContextPct: reorientPct },
|
|
1340
|
+
});
|
|
1341
|
+
// --- Boot Contract (TOP: highest-attention, never-truncated-away) -----
|
|
1342
|
+
// Everything the agent cannot afford to lose goes here, ABOVE the long
|
|
1343
|
+
// system prompt (which the model tends to skim at the tail). Identity,
|
|
1344
|
+
// the org it may write to, the user, the session, and the reorient
|
|
1345
|
+
// cadence. The long persona/protocol text follows.
|
|
1346
|
+
const orgId = identity.org_id || getResolvedTenantOrgId() || "unknown";
|
|
1347
|
+
const orgName = identity.org_name ? `${identity.org_name} ` : "";
|
|
1348
|
+
const userEmail = resolvedUserEmail || "desconhecido";
|
|
1349
|
+
const userId = resolvedUserId ? ` \`${resolvedUserId}\`` : "";
|
|
1350
|
+
const roleTxt = identity.role || identity.specialization;
|
|
1351
|
+
// Notifications (system + org) go at the very TOP, above the contract:
|
|
1352
|
+
// a critical notice must be the first thing the agent reads. The fetch
|
|
1353
|
+
// ran concurrently with bootstrapAgent, so awaiting here adds no latency.
|
|
1354
|
+
const notif = await notifPromise;
|
|
1254
1355
|
let formatted = `# Agent Bootstrap\n\n`;
|
|
1255
|
-
formatted +=
|
|
1256
|
-
if (
|
|
1257
|
-
formatted +=
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
if (
|
|
1261
|
-
formatted +=
|
|
1356
|
+
formatted += `<!-- ALERTS_SLOT -->\n`;
|
|
1357
|
+
if (notif)
|
|
1358
|
+
formatted += `${notif.trimEnd()}\n\n`;
|
|
1359
|
+
formatted += `## ⚙️ Contrato de Boot (CRÍTICO — leia e adote ANTES de tudo)\n`;
|
|
1360
|
+
formatted += `- Agente: ${identity.name || "?"} \`${agentIdStr}\`\n`;
|
|
1361
|
+
if (roleTxt)
|
|
1362
|
+
formatted += `- Papel: ${roleTxt}\n`;
|
|
1363
|
+
formatted += `- Organização: ${orgName}\`${orgId}\` — grave memórias SOMENTE nesta org (confira antes da 1ª escrita)\n`;
|
|
1364
|
+
formatted += `- Usuário: ${userEmail}${userId}\n`;
|
|
1365
|
+
formatted += `- MCP: frinus v${MCP_VERSION}`;
|
|
1366
|
+
if (activeSessionId)
|
|
1367
|
+
formatted += ` · Sessão: ${activeSessionId}`;
|
|
1368
|
+
if (result.persona_version !== undefined)
|
|
1369
|
+
formatted += ` · Persona: ${result.persona_version}`;
|
|
1370
|
+
formatted += `\n`;
|
|
1371
|
+
formatted += `- Reorient: a cada ${reorientEveryN} tool calls OU ~${reorientPct}% de contexto → \`agent_reorient(agent_id="${agentIdStr}")\`. O MCP conta as chamadas e anexa um aviso automático quando vencer — não dependa da sua memória.\n`;
|
|
1262
1372
|
if (identity.universe_id)
|
|
1263
|
-
formatted +=
|
|
1373
|
+
formatted += `- Universe: \`${identity.universe_id}\`\n`;
|
|
1264
1374
|
if (identity.team_id)
|
|
1265
|
-
formatted +=
|
|
1266
|
-
if (result.persona_version !== undefined)
|
|
1267
|
-
formatted += `Persona Version: ${result.persona_version}\n`;
|
|
1375
|
+
formatted += `- Team: \`${identity.team_id}\`\n`;
|
|
1268
1376
|
formatted += `\n`;
|
|
1269
1377
|
if (result.system_prompt) {
|
|
1270
1378
|
formatted += `## System Prompt\n${result.system_prompt}\n\n`;
|
|
@@ -1282,7 +1390,14 @@ const handlers = {
|
|
|
1282
1390
|
if (recentMemories.length > 0) {
|
|
1283
1391
|
formatted += `\n### Recent Memories (${recentMemories.length})\n`;
|
|
1284
1392
|
recentMemories.forEach((mem, i) => {
|
|
1285
|
-
|
|
1393
|
+
// The server sends `type` (not `memory_type`); accept both so the
|
|
1394
|
+
// label is real instead of a constant "[memory]". Surface the id so
|
|
1395
|
+
// the agent can reinforce/weaken/get it, and the importance so it can
|
|
1396
|
+
// weight it. Content cut raised from 150 to 400 chars.
|
|
1397
|
+
const t = mem.type || mem.memory_type || "memory";
|
|
1398
|
+
const idTxt = mem.id ? ` (id: ${mem.id})` : "";
|
|
1399
|
+
const imp = typeof mem.importance === "number" ? ` (importance: ${mem.importance.toFixed(2)})` : "";
|
|
1400
|
+
formatted += `${i + 1}. [${t}]${idTxt}${imp} ${mem.content?.slice(0, 400)}\n`;
|
|
1286
1401
|
});
|
|
1287
1402
|
}
|
|
1288
1403
|
const skills = runtime.skills || [];
|
|
@@ -1296,8 +1411,14 @@ const handlers = {
|
|
|
1296
1411
|
skills.forEach((s) => {
|
|
1297
1412
|
const name = s.skill_name || s.name || s.slug || s;
|
|
1298
1413
|
const slug = s.slug ? ` \`${s.slug}\`` : "";
|
|
1414
|
+
// version/permission come from the server (skill_version / permission)
|
|
1415
|
+
// and were previously discarded. They tell the agent which revision it
|
|
1416
|
+
// holds and whether it may edit the playbook.
|
|
1417
|
+
const ver = s.version || s.skill_version;
|
|
1418
|
+
const verTxt = ver ? ` v${ver}` : "";
|
|
1419
|
+
const perm = s.permission ? ` [${s.permission}]` : "";
|
|
1299
1420
|
const desc = s.description ? ` — ${s.description}` : "";
|
|
1300
|
-
formatted += `- ${name}${slug}${desc}\n`;
|
|
1421
|
+
formatted += `- ${name}${slug}${verTxt}${perm}${desc}\n`;
|
|
1301
1422
|
});
|
|
1302
1423
|
formatted += `</skills_catalog>\n`;
|
|
1303
1424
|
}
|
|
@@ -1310,6 +1431,19 @@ const handlers = {
|
|
|
1310
1431
|
if (delegateDigest && delegateDigest.count > 0 && delegateDigest.summary) {
|
|
1311
1432
|
formatted += `\n### Delegate Targets\n${delegateDigest.summary}\n`;
|
|
1312
1433
|
}
|
|
1434
|
+
// Shallow tree map (universe → concept) for orientation. The lines are
|
|
1435
|
+
// already composed and truncated server-side; print them verbatim. Use
|
|
1436
|
+
// hierarchy_find(query) for the exact node, universe_hierarchy for the
|
|
1437
|
+
// full tree.
|
|
1438
|
+
const hierarchyMap = runtime.hierarchy_map || [];
|
|
1439
|
+
if (Array.isArray(hierarchyMap) && hierarchyMap.length > 0) {
|
|
1440
|
+
formatted += `\n### Árvore (navegação)\n`;
|
|
1441
|
+
formatted += `Mapa raso para orientação; use hierarchy_find(query) para achar o nó exato e universe_hierarchy para a árvore completa.\n`;
|
|
1442
|
+
hierarchyMap.forEach((line) => {
|
|
1443
|
+
if (typeof line === "string" && line)
|
|
1444
|
+
formatted += `- ${line}\n`;
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1313
1447
|
return { content: [{ type: "text", text: formatted }] };
|
|
1314
1448
|
}
|
|
1315
1449
|
catch (error) {
|
|
@@ -1322,6 +1456,10 @@ const handlers = {
|
|
|
1322
1456
|
async agent_reorient(args, { memoryClient }) {
|
|
1323
1457
|
try {
|
|
1324
1458
|
const result = await memoryClient.reorientAgent(args.agent_id, args.focus);
|
|
1459
|
+
// Deterministic cadence: reorienting clears the "reorient due" counter so
|
|
1460
|
+
// the auto-appended banner stops until the next N tool calls elapse. Done
|
|
1461
|
+
// regardless of what the server returns.
|
|
1462
|
+
resetReorientCounter();
|
|
1325
1463
|
const identity = result.identity || {};
|
|
1326
1464
|
let formatted = `# Agent Reorient\n\n`;
|
|
1327
1465
|
formatted += `## Identity\n`;
|
|
@@ -2316,6 +2454,151 @@ const handlers = {
|
|
|
2316
2454
|
throw error;
|
|
2317
2455
|
}
|
|
2318
2456
|
},
|
|
2457
|
+
// ==========================================================================
|
|
2458
|
+
// Platform Notifications
|
|
2459
|
+
// ==========================================================================
|
|
2460
|
+
async notifications_list(_args, { cpClient }) {
|
|
2461
|
+
try {
|
|
2462
|
+
// mark_delivered=false: this is a read-only peek that must NOT consume a
|
|
2463
|
+
// delivery against max_deliveries. Use a higher limit than the automatic
|
|
2464
|
+
// channels so the user can see everything still pending.
|
|
2465
|
+
const inbox = await cpClient.getNotificationInbox({
|
|
2466
|
+
channel: "mcp",
|
|
2467
|
+
limit: 10,
|
|
2468
|
+
markDelivered: false,
|
|
2469
|
+
});
|
|
2470
|
+
const items = inbox?.items || [];
|
|
2471
|
+
if (items.length === 0) {
|
|
2472
|
+
return { content: [{ type: "text", text: "No pending Frinus notifications." }] };
|
|
2473
|
+
}
|
|
2474
|
+
let text = `${items.length} pending Frinus notification${items.length === 1 ? "" : "s"}:\n\n`;
|
|
2475
|
+
items.forEach((it) => {
|
|
2476
|
+
const sev = it.severity ? `[${it.severity}] ` : "";
|
|
2477
|
+
const scope = it.scope === "organization" ? "(Organização) " : "(Sistema) ";
|
|
2478
|
+
text += `- ${scope}${sev}${it.title} (id: ${it.id})\n`;
|
|
2479
|
+
let body = (it.body || "").trim();
|
|
2480
|
+
if (body.length > 300)
|
|
2481
|
+
body = body.slice(0, 300).trimEnd() + "…";
|
|
2482
|
+
if (body)
|
|
2483
|
+
text += ` ${body.replace(/\n+/g, " ")}\n`;
|
|
2484
|
+
if (it.cta_url)
|
|
2485
|
+
text += ` → ${it.cta_label ? `${it.cta_label}: ` : ""}${it.cta_url}\n`;
|
|
2486
|
+
});
|
|
2487
|
+
text += `\nCall notification_ack(id) once the user has seen one so it stops appearing.`;
|
|
2488
|
+
return { content: [{ type: "text", text }] };
|
|
2489
|
+
}
|
|
2490
|
+
catch (error) {
|
|
2491
|
+
if (error?.response?.status === 404) {
|
|
2492
|
+
// CP without the notifications endpoint yet — treat as empty, not error.
|
|
2493
|
+
return { content: [{ type: "text", text: "No pending Frinus notifications." }] };
|
|
2494
|
+
}
|
|
2495
|
+
const apiErr = handleApiError(error);
|
|
2496
|
+
if (apiErr)
|
|
2497
|
+
return apiErr;
|
|
2498
|
+
throw error;
|
|
2499
|
+
}
|
|
2500
|
+
},
|
|
2501
|
+
async notification_ack(args, { cpClient }) {
|
|
2502
|
+
const id = args.id || "";
|
|
2503
|
+
if (!id) {
|
|
2504
|
+
return {
|
|
2505
|
+
content: [{ type: "text", text: "notification_ack requires an 'id'." }],
|
|
2506
|
+
isError: true,
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
try {
|
|
2510
|
+
await cpClient.ackNotification(id);
|
|
2511
|
+
return {
|
|
2512
|
+
content: [{
|
|
2513
|
+
type: "text",
|
|
2514
|
+
text: `Notification ${id} acknowledged. It will no longer appear in the inbox.`,
|
|
2515
|
+
}],
|
|
2516
|
+
};
|
|
2517
|
+
}
|
|
2518
|
+
catch (error) {
|
|
2519
|
+
if (error?.response?.status === 404) {
|
|
2520
|
+
return {
|
|
2521
|
+
content: [{
|
|
2522
|
+
type: "text",
|
|
2523
|
+
text: `Notification ${id} not found (already acknowledged, expired, or not for you).`,
|
|
2524
|
+
}],
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
const apiErr = handleApiError(error);
|
|
2528
|
+
if (apiErr)
|
|
2529
|
+
return apiErr;
|
|
2530
|
+
throw error;
|
|
2531
|
+
}
|
|
2532
|
+
},
|
|
2533
|
+
async notification_create(args, { cpClient }) {
|
|
2534
|
+
const title = args.title?.trim() || "";
|
|
2535
|
+
if (!title) {
|
|
2536
|
+
return {
|
|
2537
|
+
content: [{ type: "text", text: "notification_create requires a non-empty 'title'." }],
|
|
2538
|
+
isError: true,
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
// Owner scope only: the Control Plane forces scope='organization' and binds
|
|
2542
|
+
// the notice to the caller's own org — the owner cannot target other orgs.
|
|
2543
|
+
const payload = { title };
|
|
2544
|
+
if (typeof args.body === "string")
|
|
2545
|
+
payload.body = args.body;
|
|
2546
|
+
if (typeof args.kind === "string")
|
|
2547
|
+
payload.kind = args.kind;
|
|
2548
|
+
if (typeof args.severity === "string")
|
|
2549
|
+
payload.severity = args.severity;
|
|
2550
|
+
if (Array.isArray(args.target_roles))
|
|
2551
|
+
payload.target_roles = args.target_roles;
|
|
2552
|
+
if (Array.isArray(args.target_user_keys))
|
|
2553
|
+
payload.target_user_keys = args.target_user_keys;
|
|
2554
|
+
if (typeof args.cta_label === "string")
|
|
2555
|
+
payload.cta_label = args.cta_label;
|
|
2556
|
+
if (typeof args.cta_url === "string")
|
|
2557
|
+
payload.cta_url = args.cta_url;
|
|
2558
|
+
if (typeof args.starts_at === "string")
|
|
2559
|
+
payload.starts_at = args.starts_at;
|
|
2560
|
+
if (typeof args.expires_at === "string")
|
|
2561
|
+
payload.expires_at = args.expires_at;
|
|
2562
|
+
if (typeof args.max_deliveries === "number")
|
|
2563
|
+
payload.max_deliveries = args.max_deliveries;
|
|
2564
|
+
try {
|
|
2565
|
+
const created = await cpClient.createOrgNotification(payload);
|
|
2566
|
+
return {
|
|
2567
|
+
content: [{
|
|
2568
|
+
type: "text",
|
|
2569
|
+
text: `Organization notification created (id: ${created?.id ?? "?"}). It will reach the ` +
|
|
2570
|
+
`members of your org on their next bootstrap/heartbeat, newest-critical first, ` +
|
|
2571
|
+
`and leaves each member's inbox on ack or after max_deliveries days unseen.`,
|
|
2572
|
+
}],
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
catch (error) {
|
|
2576
|
+
const status = error?.response?.status;
|
|
2577
|
+
if (status === 403) {
|
|
2578
|
+
return {
|
|
2579
|
+
content: [{
|
|
2580
|
+
type: "text",
|
|
2581
|
+
text: "Only an org owner can create organization notifications. This account is not an owner of the org.",
|
|
2582
|
+
}],
|
|
2583
|
+
isError: true,
|
|
2584
|
+
};
|
|
2585
|
+
}
|
|
2586
|
+
if (status === 422) {
|
|
2587
|
+
const detail = error?.response?.data?.detail;
|
|
2588
|
+
return {
|
|
2589
|
+
content: [{
|
|
2590
|
+
type: "text",
|
|
2591
|
+
text: `notification_create rejected: ${typeof detail === "string" ? detail : JSON.stringify(detail)}`,
|
|
2592
|
+
}],
|
|
2593
|
+
isError: true,
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
const apiErr = handleApiError(error);
|
|
2597
|
+
if (apiErr)
|
|
2598
|
+
return apiErr;
|
|
2599
|
+
throw error;
|
|
2600
|
+
}
|
|
2601
|
+
},
|
|
2319
2602
|
};
|
|
2320
2603
|
// ---------------------------------------------------------------------------
|
|
2321
2604
|
// Dispatcher: looks up the handler and executes it with error handling
|
package/dist/types/index.d.ts
CHANGED
|
@@ -58,6 +58,41 @@ export interface AgentClientInterface {
|
|
|
58
58
|
timeout_seconds?: number;
|
|
59
59
|
}): Promise<any>;
|
|
60
60
|
}
|
|
61
|
+
/** A single platform notification served by the Control Plane inbox. */
|
|
62
|
+
export interface NotificationItem {
|
|
63
|
+
id: string;
|
|
64
|
+
slug: string;
|
|
65
|
+
title: string;
|
|
66
|
+
body: string;
|
|
67
|
+
kind: string;
|
|
68
|
+
severity: string;
|
|
69
|
+
/** "system" (platform-wide) or "organization" (owner of this org). */
|
|
70
|
+
scope?: string;
|
|
71
|
+
cta_label?: string | null;
|
|
72
|
+
cta_url?: string | null;
|
|
73
|
+
starts_at?: string | null;
|
|
74
|
+
expires_at?: string | null;
|
|
75
|
+
}
|
|
76
|
+
/** Payload an org owner sends to publish an organization-scoped notice. */
|
|
77
|
+
export interface OwnerNotificationCreate {
|
|
78
|
+
title: string;
|
|
79
|
+
body?: string;
|
|
80
|
+
kind?: string;
|
|
81
|
+
severity?: string;
|
|
82
|
+
target_roles?: string[];
|
|
83
|
+
target_user_keys?: string[];
|
|
84
|
+
cta_label?: string | null;
|
|
85
|
+
cta_url?: string | null;
|
|
86
|
+
starts_at?: string | null;
|
|
87
|
+
expires_at?: string | null;
|
|
88
|
+
max_deliveries?: number;
|
|
89
|
+
is_active?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/** Response shape of GET /api/v1/notifications/inbox. */
|
|
92
|
+
export interface NotificationInbox {
|
|
93
|
+
items: NotificationItem[];
|
|
94
|
+
count: number;
|
|
95
|
+
}
|
|
61
96
|
/** Public surface of the CpClient used by handlers. */
|
|
62
97
|
export interface CpClientInterface {
|
|
63
98
|
createUniverse(orgId: string, data: {
|
|
@@ -71,6 +106,13 @@ export interface CpClientInterface {
|
|
|
71
106
|
description?: string;
|
|
72
107
|
}): Promise<any>;
|
|
73
108
|
deleteUniverse(orgId: string, universeId: string): Promise<void>;
|
|
109
|
+
getNotificationInbox(opts?: {
|
|
110
|
+
channel?: string;
|
|
111
|
+
limit?: number;
|
|
112
|
+
markDelivered?: boolean;
|
|
113
|
+
}): Promise<NotificationInbox>;
|
|
114
|
+
ackNotification(id: string): Promise<void>;
|
|
115
|
+
createOrgNotification(data: OwnerNotificationCreate): Promise<any>;
|
|
74
116
|
storeCredential(integrationRef: string, data: Record<string, unknown>, metadata?: Record<string, unknown>): Promise<any>;
|
|
75
117
|
getCredential(integrationRef: string): Promise<any>;
|
|
76
118
|
listCredentials(): Promise<any>;
|
|
@@ -108,6 +150,7 @@ export interface MemoryClientInterface {
|
|
|
108
150
|
created_by_user_id?: string;
|
|
109
151
|
metadata?: Record<string, unknown>;
|
|
110
152
|
context_id?: string;
|
|
153
|
+
context_path?: string;
|
|
111
154
|
credential_ref?: string;
|
|
112
155
|
universe_id?: string;
|
|
113
156
|
}): Promise<any>;
|
|
@@ -117,6 +160,11 @@ export interface MemoryClientInterface {
|
|
|
117
160
|
memory_types?: string[];
|
|
118
161
|
limit?: number;
|
|
119
162
|
}): Promise<any>;
|
|
163
|
+
findHierarchy(data: {
|
|
164
|
+
query_text: string;
|
|
165
|
+
universe_id?: string;
|
|
166
|
+
limit?: number;
|
|
167
|
+
}): Promise<any>;
|
|
120
168
|
getMemory(memoryId: string): Promise<any>;
|
|
121
169
|
getAgentMemories(agentId: string, memoryType?: string, limit?: number): Promise<any>;
|
|
122
170
|
deleteMemory(memoryId: string): Promise<any>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the MCP server version.
|
|
3
|
+
*
|
|
4
|
+
* Imported by `index.ts` (server `name`/`version` handshake) and by the
|
|
5
|
+
* `agent_bootstrap` Boot Contract block so the version the user sees always
|
|
6
|
+
* matches the running build. Keep in sync with `package.json` and
|
|
7
|
+
* `manifest.json` at release time.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MCP_VERSION = "3.8.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the MCP server version.
|
|
3
|
+
*
|
|
4
|
+
* Imported by `index.ts` (server `name`/`version` handshake) and by the
|
|
5
|
+
* `agent_bootstrap` Boot Contract block so the version the user sees always
|
|
6
|
+
* matches the running build. Keep in sync with `package.json` and
|
|
7
|
+
* `manifest.json` at release time.
|
|
8
|
+
*/
|
|
9
|
+
export const MCP_VERSION = "3.8.0";
|
package/manifest.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"manifest_version": "0.3",
|
|
3
3
|
"name": "frinus",
|
|
4
4
|
"display_name": "Frinus — Memória Cognitiva",
|
|
5
|
-
"version": "3.
|
|
6
|
-
"description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς).
|
|
5
|
+
"version": "3.8.0",
|
|
6
|
+
"description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς). 91 ferramentas de memória, sessões, agentes e conhecimento.",
|
|
7
7
|
"long_description": "O Frinus dá ao Claude memória de longo prazo, identidade e inteligência persistente entre conversas. Conecta o Claude Desktop à plataforma Frinus (Memory Engine, Control Plane e Agent Service) via MCP. Basta colar sua chave de API pessoal — nada para instalar ou compilar.",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Frinus",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frinus-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.8.0",
|
|
4
4
|
"description": "Frinus - MCP Server for Cognitive Memory (φρήν + νοῦς). Long-term memory, sessions, agents and knowledge for Claude and any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|