frinus-mcp 3.6.3 → 3.6.4

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/dist/index.js CHANGED
@@ -64,7 +64,7 @@ async function main() {
64
64
  }
65
65
  const server = new Server({
66
66
  name: "frinus",
67
- version: "3.6.3",
67
+ version: "3.6.4",
68
68
  }, {
69
69
  capabilities: {
70
70
  tools: {},
@@ -9,16 +9,19 @@ export const TOOLS = [
9
9
  Use this to record important information, learnings, decisions, or procedures
10
10
  that should be remembered for future reference.
11
11
 
12
- The backend classifies the memory by content at write time and returns a
13
- SUGGESTION (status "proposed"/"universe_only") of which universe/context node it
14
- belongs to — nothing is moved automatically. Review pending suggestions with
15
- memory_classification_pending and apply them with memory_classification_approve.
12
+ The backend classifies the memory by content at write time and proposes which
13
+ universe/context node it belongs to. When the tenant has auto-classification
14
+ enabled and the proposal's confidence meets the tenant threshold (the platform
15
+ target is 0.60), the backend ANCHORS the memory on that node automatically — it
16
+ is moved without review. Below the threshold (or with auto-classification off)
17
+ the proposal stays a PENDING suggestion: review it with
18
+ memory_classification_pending and apply with memory_classification_approve.
16
19
 
17
20
  Passing universe_id or context_id anchors the memory THERE directly (status
18
21
  "manual"): auto-classification is skipped entirely, so no suggestion is returned
19
22
  and nothing may later move it. The response always reports the destination the
20
- memory actually landed in — a suggestion, when present, is labelled as pending
21
- and not applied.
23
+ memory actually landed in — a suggestion that is still pending is labelled as
24
+ such and has NOT been applied.
22
25
 
23
26
  Memory types:
24
27
  - episodic: Specific experiences and events (what happened)
@@ -678,8 +681,15 @@ Useful for maintaining consistency in the knowledge base.`,
678
681
  inputSchema: {
679
682
  type: "object",
680
683
  properties: {
681
- similarity_threshold: { type: "number", description: "Similarity threshold 0-1 (default: 0.9)" },
682
- limit: { type: "integer", description: "Maximum results (default: 50)" },
684
+ // NOTE: the backend (POST /consolidation/detect-conflicts →
685
+ // ConflictDetectionRequest) does NOT accept similarity_threshold for
686
+ // conflict detection — conflict pairs are found by the forgetting
687
+ // manager's own analysis, and the field is silently ignored (the
688
+ // request model inherits _IgnoreExtra). It is kept here only for
689
+ // forward compatibility; it currently has no effect. Redundant
690
+ // detection (consolidation_detect_redundant) DOES honour it.
691
+ similarity_threshold: { type: "number", description: "Currently IGNORED by the backend for conflict detection (kept for forward compatibility). Use consolidation_detect_redundant to tune a similarity threshold." },
692
+ limit: { type: "integer", description: "Maximum memories to analyze (default: 50)" },
683
693
  },
684
694
  required: [],
685
695
  },
@@ -365,14 +365,17 @@ const handlers = {
365
365
  const userId = args.user_id || resolvedUserId || undefined;
366
366
  const meta = args.metadata || {};
367
367
  const universeId = args.universe_id || meta.universe_id || undefined;
368
- // Human-in-the-loop classification: the backend classifies the memory by
369
- // content at write time and returns a SUGGESTION (classification_status
370
- // "proposed"). Nothing is moved automatically — the user reviews via
371
- // memory_classification_pending and approves with
372
- // memory_classification_approve. The MCP layer no longer injects an
373
- // implicit "universe" scope nor surfaces "available universes"; scope,
374
- // universe_id and context_id are forwarded ONLY when the caller sets them
375
- // (explicit overrides → classification_status "manual").
368
+ // Write-time classification: the backend classifies the memory by content
369
+ // and, when the tenant's `auto_classify` setting is enabled and the
370
+ // proposal confidence meets the tenant threshold, anchors it automatically
371
+ // (classification_status "proposed"/"universe_only" become real FK columns);
372
+ // below the threshold it stays a pending SUGGESTION the user reviews via
373
+ // memory_classification_pending / memory_classification_approve. See
374
+ // memory/src/api/routers/memories.py (classification/settings). The MCP
375
+ // layer no longer injects an implicit "universe" scope nor surfaces
376
+ // "available universes"; scope, universe_id and context_id are forwarded
377
+ // ONLY when the caller sets them (explicit overrides → status "manual",
378
+ // which anchors there directly and skips classification).
376
379
  try {
377
380
  const result = await memoryClient.storeMemory({
378
381
  agent_id: args.agent_id,
@@ -890,7 +893,18 @@ const handlers = {
890
893
  }
891
894
  formatted += `\n`;
892
895
  results.memories.forEach((mem, i) => {
893
- formatted += `${i + 1}. [${mem.memory_type}] (id: ${mem.id}) (score: ${mem.weighted_score?.toFixed(2) || mem.similarity?.toFixed(2)})\n`;
896
+ // Q-MEM-02: /memories/search-with-attention returns `attention_score`
897
+ // (re-ranked) + `base_similarity` (see memory/src/api/schemas.py
898
+ // MemoryWithAttentionScore). The legacy field `weighted_score` and the
899
+ // plain /memories/search field `similarity` are kept as fallbacks so the
900
+ // same renderer works across endpoints. `?? ` (not `||`) so a real 0 score
901
+ // is not swallowed.
902
+ const score = mem.attention_score ??
903
+ mem.weighted_score ??
904
+ mem.base_similarity ??
905
+ mem.similarity;
906
+ const scoreTxt = typeof score === "number" ? score.toFixed(2) : "n/a";
907
+ formatted += `${i + 1}. [${mem.memory_type}] (id: ${mem.id}) (score: ${scoreTxt})\n`;
894
908
  formatted += ` ${mem.content?.slice(0, 200)}...\n\n`;
895
909
  });
896
910
  return { content: [{ type: "text", text: formatted }] };
@@ -1035,11 +1049,27 @@ const handlers = {
1035
1049
  if (!conflicts || conflicts.length === 0) {
1036
1050
  return { content: [{ type: "text", text: "No conflicts detected." }] };
1037
1051
  }
1038
- let formatted = `Detected ${result.conflicts_found || conflicts.length} potential conflicts:\n\n`;
1052
+ let formatted = `Detected ${result.conflicts_found ?? conflicts.length} potential conflicts:\n\n`;
1039
1053
  conflicts.forEach((conflict, i) => {
1040
- formatted += `${i + 1}. Similarity: ${conflict.similarity?.toFixed(2)}\n`;
1041
- formatted += ` Memory 1: ${conflict.memory1?.content?.slice(0, 100)}...\n`;
1042
- formatted += ` Memory 2: ${conflict.memory2?.content?.slice(0, 100)}...\n\n`;
1054
+ // Q-MEM-03: the backend emits `memory_1` / `memory_2` (ConflictPair in
1055
+ // memory/src/api/schemas.py), each with id/content, plus `conflict_type`
1056
+ // and `suggested_action`. The old code read `memory1`/`memory2` (no
1057
+ // underscore) so content printed as "undefined" and the type/action were
1058
+ // dropped. Keep the snake-less variants as a defensive fallback.
1059
+ const m1 = conflict.memory_1 ?? conflict.memory1;
1060
+ const m2 = conflict.memory_2 ?? conflict.memory2;
1061
+ const simTxt = typeof conflict.similarity === "number"
1062
+ ? conflict.similarity.toFixed(2)
1063
+ : "n/a";
1064
+ formatted += `${i + 1}. Similarity: ${simTxt}`;
1065
+ if (conflict.conflict_type)
1066
+ formatted += ` · type: ${conflict.conflict_type}`;
1067
+ formatted += `\n`;
1068
+ formatted += ` Memory 1 (${m1?.id ?? "?"}): ${m1?.content?.slice(0, 100) ?? "(no content)"}...\n`;
1069
+ formatted += ` Memory 2 (${m2?.id ?? "?"}): ${m2?.content?.slice(0, 100) ?? "(no content)"}...\n`;
1070
+ if (conflict.suggested_action)
1071
+ formatted += ` Suggested action: ${conflict.suggested_action}\n`;
1072
+ formatted += `\n`;
1043
1073
  });
1044
1074
  return { content: [{ type: "text", text: formatted }] };
1045
1075
  },
@@ -1062,12 +1092,19 @@ const handlers = {
1062
1092
  if (!groups || groups.length === 0) {
1063
1093
  return { content: [{ type: "text", text: "No redundant memories detected." }] };
1064
1094
  }
1065
- let formatted = `Detected ${result.groups_found || groups.length} redundant memory groups (${result.total_redundant || 0} total memories):\n\n`;
1095
+ let formatted = `Detected ${result.groups_found ?? groups.length} redundant memory groups (${result.total_redundant ?? 0} redundant memories):\n\n`;
1066
1096
  groups.forEach((group, i) => {
1067
- formatted += `${i + 1}. Group similarity: ${group.avg_similarity?.toFixed(2) || 'N/A'}\n`;
1068
- formatted += ` Memories: ${group.memory_ids?.length || 0}\n`;
1069
- if (group.memories && group.memories.length > 0) {
1070
- formatted += ` Preview: ${group.memories[0]?.content?.slice(0, 100)}...\n`;
1097
+ // Q-MEM-04: the backend emits `keep` (the memory to retain), `redundant`
1098
+ // (the duplicates) and `total_in_group` (RedundantGroup in
1099
+ // memory/src/api/schemas.py). The old code read `avg_similarity`,
1100
+ // `memory_ids` and `memories`, none of which exist, so every group showed
1101
+ // "N/A" and 0 memories.
1102
+ const redundantCount = Array.isArray(group.redundant) ? group.redundant.length : 0;
1103
+ const total = group.total_in_group ?? (redundantCount + (group.keep ? 1 : 0));
1104
+ formatted += `${i + 1}. Memories in group: ${total} (keep 1, redundant ${redundantCount})\n`;
1105
+ const keepPreview = group.keep?.content?.slice(0, 100);
1106
+ if (keepPreview) {
1107
+ formatted += ` Keep (${group.keep?.id ?? "?"}): ${keepPreview}...\n`;
1071
1108
  }
1072
1109
  formatted += `\n`;
1073
1110
  });
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "frinus",
4
4
  "display_name": "Frinus — Memória Cognitiva",
5
- "version": "3.6.3",
5
+ "version": "3.6.4",
6
6
  "description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς). 83 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": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frinus-mcp",
3
- "version": "3.6.3",
3
+ "version": "3.6.4",
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",
@@ -17,6 +17,7 @@
17
17
  },
18
18
  "scripts": {
19
19
  "build": "tsc",
20
+ "test": "node --import tsx --test tests/*.test.ts",
20
21
  "start": "node dist/index.js",
21
22
  "dev": "tsx src/index.ts",
22
23
  "pack:mcpb": "./scripts/pack-mcpb.sh",