frinus-mcp 3.3.0 → 3.4.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/README.md +1 -1
- package/dist/client/memory-client.d.ts +15 -0
- package/dist/client/memory-client.js +40 -0
- package/dist/tools/definitions.js +71 -2
- package/dist/tools/handlers.js +261 -62
- package/dist/types/index.d.ts +8 -0
- package/manifest.json +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ It does **not** run on its own. It stores nothing locally: every tool call is fo
|
|
|
11
11
|
|
|
12
12
|
With those in place, the server exposes **70 tools** spanning cognitive memory, working memory, sessions, agents, the L0–L3 knowledge hierarchy, orchestration tasks, and training pipelines. It speaks stdio and works with any MCP-aware client.
|
|
13
13
|
|
|
14
|
-
- **Version:** 3.
|
|
14
|
+
- **Version:** 3.4.1
|
|
15
15
|
- **Tools:** 70 (see [Tools Reference](#tools-reference))
|
|
16
16
|
- **Platform:** https://frinus.rdxsec.com.br (hosted SaaS — the backend this server talks to)
|
|
17
17
|
- **npm package:** [`frinus-mcp`](https://www.npmjs.com/package/frinus-mcp)
|
|
@@ -55,6 +55,21 @@ export declare class MemoryClient implements MemoryClientInterface {
|
|
|
55
55
|
task_description: string;
|
|
56
56
|
max_tokens?: number;
|
|
57
57
|
}): Promise<any>;
|
|
58
|
+
/** List memories with a classification suggestion awaiting approval. */
|
|
59
|
+
getPendingClassifications(limit?: number): Promise<any>;
|
|
60
|
+
/**
|
|
61
|
+
* Approve a proposed classification. Without overrides, the backend applies
|
|
62
|
+
* its own suggestion. Passing universe_id and/or context_id overrides the
|
|
63
|
+
* suggested target (also used to CORRECT a wrong suggestion).
|
|
64
|
+
*/
|
|
65
|
+
approveClassification(memoryId: string, data?: {
|
|
66
|
+
universe_id?: string;
|
|
67
|
+
context_id?: string;
|
|
68
|
+
}): Promise<any>;
|
|
69
|
+
/** Reject a proposed classification (memory stays where it is). */
|
|
70
|
+
rejectClassification(memoryId: string): Promise<any>;
|
|
71
|
+
/** Approve multiple proposed classifications in one call. */
|
|
72
|
+
approveClassificationBatch(ids: string[]): Promise<any>;
|
|
58
73
|
reinforceMemory(memoryId: string, boost?: number): Promise<any>;
|
|
59
74
|
weakenMemory(memoryId: string, penalty?: number): Promise<any>;
|
|
60
75
|
getWorkingMemory(contextId: string): Promise<any>;
|
|
@@ -142,6 +142,46 @@ export class MemoryClient {
|
|
|
142
142
|
return response.data;
|
|
143
143
|
}
|
|
144
144
|
// =========================================================================
|
|
145
|
+
// Auto-classification (human-in-the-loop)
|
|
146
|
+
//
|
|
147
|
+
// The backend classifies a memory by content at write time and returns a
|
|
148
|
+
// SUGGESTION with classification_status="proposed". The user reviews the
|
|
149
|
+
// pending queue and explicitly approves (optionally overriding the target)
|
|
150
|
+
// or rejects. Nothing is moved automatically.
|
|
151
|
+
// =========================================================================
|
|
152
|
+
/** List memories with a classification suggestion awaiting approval. */
|
|
153
|
+
async getPendingClassifications(limit) {
|
|
154
|
+
const params = limit ? `?limit=${limit}` : "";
|
|
155
|
+
const response = await this.client.get(`/memories/classification/pending${params}`);
|
|
156
|
+
return response.data;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Approve a proposed classification. Without overrides, the backend applies
|
|
160
|
+
* its own suggestion. Passing universe_id and/or context_id overrides the
|
|
161
|
+
* suggested target (also used to CORRECT a wrong suggestion).
|
|
162
|
+
*/
|
|
163
|
+
async approveClassification(memoryId, data) {
|
|
164
|
+
const payload = {};
|
|
165
|
+
if (data?.universe_id)
|
|
166
|
+
payload.universe_id = data.universe_id;
|
|
167
|
+
if (data?.context_id)
|
|
168
|
+
payload.context_id = data.context_id;
|
|
169
|
+
const response = await this.client.post(`/memories/${memoryId}/classification/approve`, payload);
|
|
170
|
+
return response.data;
|
|
171
|
+
}
|
|
172
|
+
/** Reject a proposed classification (memory stays where it is). */
|
|
173
|
+
async rejectClassification(memoryId) {
|
|
174
|
+
const response = await this.client.post(`/memories/${memoryId}/classification/reject`);
|
|
175
|
+
return response.data;
|
|
176
|
+
}
|
|
177
|
+
/** Approve multiple proposed classifications in one call. */
|
|
178
|
+
async approveClassificationBatch(ids) {
|
|
179
|
+
const response = await this.client.post("/memories/classification/approve-batch", {
|
|
180
|
+
ids,
|
|
181
|
+
});
|
|
182
|
+
return response.data;
|
|
183
|
+
}
|
|
184
|
+
// =========================================================================
|
|
145
185
|
// Dynamic Relevance
|
|
146
186
|
// =========================================================================
|
|
147
187
|
async reinforceMemory(memoryId, boost) {
|
|
@@ -9,6 +9,17 @@ 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.
|
|
16
|
+
|
|
17
|
+
Passing universe_id or context_id anchors the memory THERE directly (status
|
|
18
|
+
"manual"): auto-classification is skipped entirely, so no suggestion is returned
|
|
19
|
+
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.
|
|
22
|
+
|
|
12
23
|
Memory types:
|
|
13
24
|
- episodic: Specific experiences and events (what happened)
|
|
14
25
|
- semantic: General knowledge and facts (what I know)
|
|
@@ -38,7 +49,7 @@ Scopes:
|
|
|
38
49
|
scope: {
|
|
39
50
|
type: "string",
|
|
40
51
|
enum: ["user", "agent", "universe", "organization"],
|
|
41
|
-
description: "
|
|
52
|
+
description: "Optional explicit visibility scope — WHO can see the memory, independent of WHERE it is filed in the hierarchy (that is universe_id/context_id). 'organization' (org-wide), 'user' (private), 'universe' (department-shared — requires a universe, via universe_id/context_id or the agent's own), 'agent' (agent-only). Default: 'user'.",
|
|
42
53
|
},
|
|
43
54
|
importance: {
|
|
44
55
|
type: "number",
|
|
@@ -58,7 +69,7 @@ Scopes:
|
|
|
58
69
|
},
|
|
59
70
|
universe_id: {
|
|
60
71
|
type: "string",
|
|
61
|
-
description: "UUID
|
|
72
|
+
description: "Optional UUID of a universe to file the memory under explicitly. Anchors it there as ground-truth (status 'manual'): auto-classification is skipped and nothing may later move it. Omit to let the backend suggest one.",
|
|
62
73
|
},
|
|
63
74
|
context_id: {
|
|
64
75
|
type: "string",
|
|
@@ -156,6 +167,64 @@ Use with caution - this permanently removes the memory.`,
|
|
|
156
167
|
},
|
|
157
168
|
},
|
|
158
169
|
// ==========================================================================
|
|
170
|
+
// Auto-classification — human-in-the-loop (3)
|
|
171
|
+
// ==========================================================================
|
|
172
|
+
{
|
|
173
|
+
name: "memory_classification_pending",
|
|
174
|
+
description: `List memories whose auto-classification is awaiting your approval.
|
|
175
|
+
|
|
176
|
+
The backend classifies each new memory by content and proposes a target
|
|
177
|
+
universe/context node (status "proposed"). This returns that review queue with,
|
|
178
|
+
per item: the memory id, the suggested destination breadcrumb, and the
|
|
179
|
+
confidence. Approve with memory_classification_approve, or correct the target by
|
|
180
|
+
passing universe_id/context_id to it.`,
|
|
181
|
+
inputSchema: {
|
|
182
|
+
type: "object",
|
|
183
|
+
properties: {
|
|
184
|
+
limit: { type: "integer", description: "Maximum suggestions to return (default: backend default)" },
|
|
185
|
+
},
|
|
186
|
+
required: [],
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "memory_classification_approve",
|
|
191
|
+
description: `Approve a proposed classification, moving the memory to its suggested
|
|
192
|
+
universe/context node.
|
|
193
|
+
|
|
194
|
+
Without overrides, the backend applies its own suggestion. Pass universe_id
|
|
195
|
+
and/or context_id to override the suggested target (also the way to CORRECT a
|
|
196
|
+
wrong suggestion).`,
|
|
197
|
+
inputSchema: {
|
|
198
|
+
type: "object",
|
|
199
|
+
properties: {
|
|
200
|
+
memory_id: { type: "string", description: "UUID of the memory whose classification is being approved" },
|
|
201
|
+
universe_id: { type: "string", description: "Optional UUID of a universe to override the suggested destination" },
|
|
202
|
+
context_id: { type: "string", description: "Optional UUID of a hierarchy node (concept/theme/topic/point) to override the suggested destination" },
|
|
203
|
+
},
|
|
204
|
+
required: ["memory_id"],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "memory_reclassify",
|
|
209
|
+
description: `Re-classify a memory. Either pin it to an explicit destination, or ask the
|
|
210
|
+
backend to re-run its automatic classifier.
|
|
211
|
+
|
|
212
|
+
- Pass universe_id and/or context_id to set the destination directly (correction).
|
|
213
|
+
- Pass rerun_auto=true (and no explicit target) to discard the current
|
|
214
|
+
suggestion and let the backend propose a new one (review via
|
|
215
|
+
memory_classification_pending).`,
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
memory_id: { type: "string", description: "UUID of the memory to reclassify" },
|
|
220
|
+
universe_id: { type: "string", description: "Optional UUID of a universe to set as the explicit destination" },
|
|
221
|
+
context_id: { type: "string", description: "Optional UUID of a hierarchy node to set as the explicit destination" },
|
|
222
|
+
rerun_auto: { type: "boolean", description: "When true (and no explicit target), re-run the backend's automatic classification" },
|
|
223
|
+
},
|
|
224
|
+
required: ["memory_id"],
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
// ==========================================================================
|
|
159
228
|
// Dynamic Relevance (2)
|
|
160
229
|
// ==========================================================================
|
|
161
230
|
{
|
package/dist/tools/handlers.js
CHANGED
|
@@ -196,84 +196,283 @@ export function handleApiError(error) {
|
|
|
196
196
|
}
|
|
197
197
|
return null; // Not a known error, let it propagate
|
|
198
198
|
}
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
// Auto-classification rendering helpers (human-in-the-loop)
|
|
201
|
+
// ---------------------------------------------------------------------------
|
|
202
|
+
// The Memory Engine now classifies a memory by content at write time and
|
|
203
|
+
// returns a SUGGESTION with classification_status="proposed". These helpers
|
|
204
|
+
// render that suggestion enxuto, and degrade gracefully to the minimal
|
|
205
|
+
// ID/Type/Scope format when an OLDER backend omits the classification fields.
|
|
206
|
+
/** Human-readable confidence bucket from a 0..1 score. >=0.75 alta, >=0.45 média. */
|
|
207
|
+
function confidenceLabel(confidence) {
|
|
208
|
+
if (typeof confidence !== "number" || Number.isNaN(confidence))
|
|
209
|
+
return null;
|
|
210
|
+
if (confidence >= 0.75)
|
|
211
|
+
return "alta";
|
|
212
|
+
if (confidence >= 0.45)
|
|
213
|
+
return "média";
|
|
214
|
+
return "baixa";
|
|
215
|
+
}
|
|
216
|
+
/** Name of a classified universe, tolerating string | {name} shapes. */
|
|
217
|
+
function universeName(u) {
|
|
218
|
+
if (!u)
|
|
219
|
+
return null;
|
|
220
|
+
if (typeof u === "string")
|
|
221
|
+
return u;
|
|
222
|
+
if (typeof u === "object") {
|
|
223
|
+
const name = u.name;
|
|
224
|
+
if (typeof name === "string" && name)
|
|
225
|
+
return name;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
/** Breadcrumb path of a classified context node ("Checkout › Antifraude"). */
|
|
230
|
+
function contextName(c) {
|
|
231
|
+
if (!c)
|
|
232
|
+
return null;
|
|
233
|
+
if (typeof c === "string")
|
|
234
|
+
return c;
|
|
235
|
+
if (typeof c === "object") {
|
|
236
|
+
const obj = c;
|
|
237
|
+
const path = obj.path || obj.context_path || obj.name;
|
|
238
|
+
if (typeof path === "string" && path)
|
|
239
|
+
return path;
|
|
240
|
+
}
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Build the suggestion breadcrumb from a result that carries
|
|
245
|
+
* classified_universe / classified_context, e.g.
|
|
246
|
+
* "Monetizze › Checkout › Antifraude" or just "Monetizze".
|
|
247
|
+
*/
|
|
248
|
+
function suggestionLabel(result) {
|
|
249
|
+
const uni = universeName(result?.classified_universe);
|
|
250
|
+
const ctx = contextName(result?.classified_context);
|
|
251
|
+
if (uni && ctx)
|
|
252
|
+
return `${uni} › ${ctx}`;
|
|
253
|
+
if (uni)
|
|
254
|
+
return uni;
|
|
255
|
+
if (ctx)
|
|
256
|
+
return ctx;
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Build the FINAL destination breadcrumb for an approved/manual memory. Falls
|
|
261
|
+
* back to the classified_* suggestion shape, then to a bare scope.
|
|
262
|
+
*/
|
|
263
|
+
function destinationLabel(result) {
|
|
264
|
+
const fromSuggestion = suggestionLabel(result);
|
|
265
|
+
if (fromSuggestion)
|
|
266
|
+
return fromSuggestion;
|
|
267
|
+
if (typeof result?.scope === "string" && result.scope)
|
|
268
|
+
return `scope ${result.scope}`;
|
|
269
|
+
return "";
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Breadcrumb for a hierarchy chain returned by the backend as a list of nodes
|
|
273
|
+
* ({ id, level, name, context_path }), e.g. "Operacoes › Contratos › Gain-share".
|
|
274
|
+
* Falls back to the raw id for legacy FK-only nodes with no contexts row.
|
|
275
|
+
*/
|
|
276
|
+
function hierarchyLabel(nodes) {
|
|
277
|
+
if (!Array.isArray(nodes) || nodes.length === 0)
|
|
278
|
+
return "";
|
|
279
|
+
return nodes
|
|
280
|
+
.map((n) => (typeof n?.name === "string" && n.name ? n.name : n?.id))
|
|
281
|
+
.filter((s) => typeof s === "string" && s)
|
|
282
|
+
.join(" › ");
|
|
283
|
+
}
|
|
284
|
+
/** Where the memory ACTUALLY landed — never the classifier's suggestion. */
|
|
285
|
+
function appliedLabel(result) {
|
|
286
|
+
const fromChain = hierarchyLabel(result?.hierarchy);
|
|
287
|
+
if (fromChain)
|
|
288
|
+
return fromChain;
|
|
289
|
+
// Older backend without `hierarchy`: fall back to the real FK column. Still
|
|
290
|
+
// the applied destination — NOT classified_universe (that is a proposal).
|
|
291
|
+
const uni = result?.universe_id;
|
|
292
|
+
return typeof uni === "string" && uni ? uni : "";
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Render the memory_store response.
|
|
296
|
+
*
|
|
297
|
+
* Hard rule: the ONLY thing reported as the destination is where the memory was
|
|
298
|
+
* actually written (`hierarchy` / `universe_id`). `classified_*` and
|
|
299
|
+
* `proposed_hierarchy` are the auto-classifier's SUGGESTION and are always
|
|
300
|
+
* labelled as such — never as the destination. Reporting a pending suggestion
|
|
301
|
+
* as the destination is what made an explicit `universe_id` look ignored when
|
|
302
|
+
* the write had in fact honoured it.
|
|
303
|
+
*/
|
|
304
|
+
function renderStoreResult(result) {
|
|
305
|
+
const id = result?.id;
|
|
306
|
+
const status = result?.classification_status;
|
|
307
|
+
const head = `✓ Salva`;
|
|
308
|
+
const applied = appliedLabel(result);
|
|
309
|
+
const pending = status === "proposed" || status === "universe_only";
|
|
310
|
+
// Anchored: a real destination exists. Report it, and only then mention a
|
|
311
|
+
// still-pending suggestion (which cannot have been applied, by definition).
|
|
312
|
+
if (applied) {
|
|
313
|
+
let out = `${head} em: ${applied}`;
|
|
314
|
+
if (pending) {
|
|
315
|
+
const sug = hierarchyLabel(result?.proposed_hierarchy) || suggestionLabel(result);
|
|
316
|
+
if (sug && sug !== applied) {
|
|
317
|
+
out += `\n ↳ sugestão pendente (NÃO aplicada): ${sug} — revise com memory_classification_pending`;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return out;
|
|
321
|
+
}
|
|
322
|
+
// Not anchored anywhere: the suggestion is all we have to offer.
|
|
323
|
+
if (pending) {
|
|
324
|
+
const sug = hierarchyLabel(result?.proposed_hierarchy) || suggestionLabel(result);
|
|
325
|
+
const conf = confidenceLabel(result?.classification_confidence);
|
|
326
|
+
if (sug) {
|
|
327
|
+
const confTxt = conf ? `, confiança ${conf}` : "";
|
|
328
|
+
return `${head} · sem destino aplicado · sugestão: ${sug} (auto${confTxt}) — aprove com memory_classification_approve`;
|
|
329
|
+
}
|
|
330
|
+
return `${head} · sem destino aplicado e sem sugestão confiável — use memory_classification_pending para revisar`;
|
|
331
|
+
}
|
|
332
|
+
// Defensive: older backend without classification fields, or no hierarchy at all.
|
|
333
|
+
return `${head}\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}`;
|
|
334
|
+
}
|
|
199
335
|
const handlers = {
|
|
200
336
|
// ==========================================================================
|
|
201
337
|
// Core Memory
|
|
202
338
|
// ==========================================================================
|
|
203
|
-
async memory_store(args, { memoryClient,
|
|
339
|
+
async memory_store(args, { memoryClient, resolvedUserId }) {
|
|
204
340
|
const createdByUser = args.created_by_user_id || resolvedUserId || undefined;
|
|
205
341
|
const userId = args.user_id || resolvedUserId || undefined;
|
|
206
342
|
const meta = args.metadata || {};
|
|
207
343
|
const universeId = args.universe_id || meta.universe_id || undefined;
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
344
|
+
// Human-in-the-loop classification: the backend classifies the memory by
|
|
345
|
+
// content at write time and returns a SUGGESTION (classification_status
|
|
346
|
+
// "proposed"). Nothing is moved automatically — the user reviews via
|
|
347
|
+
// memory_classification_pending and approves with
|
|
348
|
+
// memory_classification_approve. The MCP layer no longer injects an
|
|
349
|
+
// implicit "universe" scope nor surfaces "available universes"; scope,
|
|
350
|
+
// universe_id and context_id are forwarded ONLY when the caller sets them
|
|
351
|
+
// (explicit overrides → classification_status "manual").
|
|
352
|
+
try {
|
|
353
|
+
const result = await memoryClient.storeMemory({
|
|
354
|
+
agent_id: args.agent_id,
|
|
355
|
+
content: args.content,
|
|
356
|
+
memory_type: args.memory_type,
|
|
357
|
+
importance: args.importance,
|
|
358
|
+
user_id: userId,
|
|
359
|
+
created_by_user_id: createdByUser,
|
|
360
|
+
metadata: args.metadata,
|
|
361
|
+
context_id: args.context_id,
|
|
362
|
+
credential_ref: args.credential_ref,
|
|
363
|
+
universe_id: universeId,
|
|
364
|
+
scope: args.scope,
|
|
365
|
+
});
|
|
366
|
+
return { content: [{ type: "text", text: renderStoreResult(result) }] };
|
|
367
|
+
}
|
|
368
|
+
catch (error) {
|
|
369
|
+
const apiErr = handleApiError(error);
|
|
370
|
+
if (apiErr)
|
|
371
|
+
return apiErr;
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
// ==========================================================================
|
|
376
|
+
// Auto-classification (human-in-the-loop)
|
|
377
|
+
// ==========================================================================
|
|
378
|
+
async memory_classification_pending(args, { memoryClient }) {
|
|
379
|
+
let result;
|
|
380
|
+
try {
|
|
381
|
+
result = await memoryClient.getPendingClassifications(args.limit);
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
const apiErr = handleApiError(error);
|
|
385
|
+
if (apiErr)
|
|
386
|
+
return apiErr;
|
|
387
|
+
throw error;
|
|
388
|
+
}
|
|
389
|
+
const items = Array.isArray(result) ? result : (result?.items || result?.pending || []);
|
|
390
|
+
if (!items || items.length === 0) {
|
|
391
|
+
return { content: [{ type: "text", text: "Nenhuma sugestão de classificação aguardando aprovação." }] };
|
|
392
|
+
}
|
|
393
|
+
let formatted = `Sugestões aguardando aprovação (${items.length}):\n\n`;
|
|
394
|
+
items.forEach((m, i) => {
|
|
395
|
+
const label = suggestionLabel(m);
|
|
396
|
+
const conf = confidenceLabel(m.confidence);
|
|
397
|
+
const preview = typeof m.content === "string"
|
|
398
|
+
? (m.content.length > 120 ? m.content.slice(0, 120) + "…" : m.content)
|
|
399
|
+
: "";
|
|
400
|
+
formatted += `${i + 1}. (id: ${m.id})`;
|
|
401
|
+
if (conf)
|
|
402
|
+
formatted += ` · confiança ${conf}`;
|
|
403
|
+
formatted += `\n sugestão: ${label}\n`;
|
|
404
|
+
if (preview)
|
|
405
|
+
formatted += ` ${preview}\n`;
|
|
406
|
+
formatted += `\n`;
|
|
407
|
+
});
|
|
408
|
+
formatted += `Aprove com memory_classification_approve(memory_id) — passe universe_id/context_id para corrigir o destino.`;
|
|
409
|
+
return { content: [{ type: "text", text: formatted }] };
|
|
410
|
+
},
|
|
411
|
+
async memory_classification_approve(args, { memoryClient }) {
|
|
412
|
+
const memoryId = args.memory_id;
|
|
413
|
+
const override = {};
|
|
414
|
+
if (args.universe_id)
|
|
415
|
+
override.universe_id = args.universe_id;
|
|
416
|
+
if (args.context_id)
|
|
417
|
+
override.context_id = args.context_id;
|
|
418
|
+
let result;
|
|
419
|
+
try {
|
|
420
|
+
result = await memoryClient.approveClassification(memoryId, override);
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
const apiErr = handleApiError(error);
|
|
424
|
+
if (apiErr)
|
|
425
|
+
return apiErr;
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
const dest = destinationLabel(result) || suggestionLabel(result);
|
|
429
|
+
const overridden = Boolean(override.universe_id || override.context_id);
|
|
430
|
+
let text = `✓ Classificação aprovada`;
|
|
431
|
+
if (dest)
|
|
432
|
+
text += ` · ${dest}`;
|
|
433
|
+
if (overridden)
|
|
434
|
+
text += ` (destino corrigido pelo usuário)`;
|
|
435
|
+
text += `\nID: ${memoryId}`;
|
|
436
|
+
return { content: [{ type: "text", text }] };
|
|
437
|
+
},
|
|
438
|
+
async memory_reclassify(args, { memoryClient }) {
|
|
439
|
+
const memoryId = args.memory_id;
|
|
440
|
+
const rerunAuto = args.rerun_auto;
|
|
441
|
+
const universeId = args.universe_id;
|
|
442
|
+
const contextId = args.context_id;
|
|
228
443
|
try {
|
|
229
|
-
|
|
444
|
+
// Correction = approve with an override (or reject to re-run the
|
|
445
|
+
// backend's auto-classifier when no explicit target is given).
|
|
446
|
+
if (universeId || contextId) {
|
|
447
|
+
const result = await memoryClient.approveClassification(memoryId, {
|
|
448
|
+
universe_id: universeId,
|
|
449
|
+
context_id: contextId,
|
|
450
|
+
});
|
|
451
|
+
const dest = destinationLabel(result) || suggestionLabel(result);
|
|
452
|
+
let text = `✓ Reclassificada`;
|
|
453
|
+
if (dest)
|
|
454
|
+
text += ` · ${dest}`;
|
|
455
|
+
text += ` (destino definido pelo usuário)\nID: ${memoryId}`;
|
|
456
|
+
return { content: [{ type: "text", text }] };
|
|
457
|
+
}
|
|
458
|
+
if (rerunAuto) {
|
|
459
|
+
const result = await memoryClient.rejectClassification(memoryId);
|
|
460
|
+
const dest = suggestionLabel(result);
|
|
461
|
+
let text = `✓ Sugestão anterior descartada; reclassificação automática solicitada`;
|
|
462
|
+
if (dest)
|
|
463
|
+
text += ` · nova sugestão: ${dest}`;
|
|
464
|
+
text += `\nID: ${memoryId}\nRevise em memory_classification_pending.`;
|
|
465
|
+
return { content: [{ type: "text", text }] };
|
|
466
|
+
}
|
|
230
467
|
return {
|
|
231
468
|
content: [{
|
|
232
469
|
type: "text",
|
|
233
|
-
text:
|
|
470
|
+
text: "memory_reclassify requer universe_id/context_id (destino corrigido) ou rerun_auto=true (reexecutar classificação automática).",
|
|
234
471
|
}],
|
|
472
|
+
isError: true,
|
|
235
473
|
};
|
|
236
474
|
}
|
|
237
475
|
catch (error) {
|
|
238
|
-
// Fallback: scope was implicitly "universe" but the agent has no
|
|
239
|
-
// resolvable universe (backend returns 400 "must be linked to a
|
|
240
|
-
// universe"). Retry as `agent` scope and surface available universes.
|
|
241
|
-
const status = error?.response?.status;
|
|
242
|
-
const detail = String(error?.response?.data?.detail ?? "");
|
|
243
|
-
const isMissingUniverse = status === 400 &&
|
|
244
|
-
!explicitScope &&
|
|
245
|
-
!universeId &&
|
|
246
|
-
detail.includes("must be linked to a universe");
|
|
247
|
-
if (isMissingUniverse) {
|
|
248
|
-
try {
|
|
249
|
-
const result = await memoryClient.storeMemory({ ...baseInput, scope: "agent" });
|
|
250
|
-
let text = `Memory stored successfully.\nID: ${result.id}\nType: ${result.memory_type}\nScope: ${result.scope}`;
|
|
251
|
-
// Enrich with available universes to help future categorization.
|
|
252
|
-
try {
|
|
253
|
-
const orgId = getResolvedTenantOrgId();
|
|
254
|
-
if (orgId) {
|
|
255
|
-
const list = await cpClient.listUniverses(orgId);
|
|
256
|
-
const universes = Array.isArray(list) ? list : (list.universes || list.items || []);
|
|
257
|
-
if (universes && universes.length > 0) {
|
|
258
|
-
text += `\n\nDica: esta memória não foi associada a um universo (salva como memória do agente). Para categorizá-la, informe universe_id na próxima vez. Universos disponíveis:`;
|
|
259
|
-
universes.forEach((u) => {
|
|
260
|
-
text += `\n- ${u.name} (id: ${u.id})`;
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
// Listing universes is best-effort; never break a successful store.
|
|
267
|
-
}
|
|
268
|
-
return { content: [{ type: "text", text }] };
|
|
269
|
-
}
|
|
270
|
-
catch (retryError) {
|
|
271
|
-
const retryApiErr = handleApiError(retryError);
|
|
272
|
-
if (retryApiErr)
|
|
273
|
-
return retryApiErr;
|
|
274
|
-
throw retryError;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
476
|
const apiErr = handleApiError(error);
|
|
278
477
|
if (apiErr)
|
|
279
478
|
return apiErr;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -106,6 +106,7 @@ export interface MemoryClientInterface {
|
|
|
106
106
|
metadata?: Record<string, unknown>;
|
|
107
107
|
context_id?: string;
|
|
108
108
|
credential_ref?: string;
|
|
109
|
+
universe_id?: string;
|
|
109
110
|
}): Promise<any>;
|
|
110
111
|
searchMemories(data: {
|
|
111
112
|
query_text: string;
|
|
@@ -121,6 +122,13 @@ export interface MemoryClientInterface {
|
|
|
121
122
|
task_description: string;
|
|
122
123
|
max_tokens?: number;
|
|
123
124
|
}): Promise<any>;
|
|
125
|
+
getPendingClassifications(limit?: number): Promise<any>;
|
|
126
|
+
approveClassification(memoryId: string, data?: {
|
|
127
|
+
universe_id?: string;
|
|
128
|
+
context_id?: string;
|
|
129
|
+
}): Promise<any>;
|
|
130
|
+
rejectClassification(memoryId: string): Promise<any>;
|
|
131
|
+
approveClassificationBatch(ids: string[]): Promise<any>;
|
|
124
132
|
reinforceMemory(memoryId: string, boost?: number): Promise<any>;
|
|
125
133
|
weakenMemory(memoryId: string, penalty?: number): Promise<any>;
|
|
126
134
|
getWorkingMemory(contextId: string): Promise<any>;
|
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.1
|
|
6
|
-
"description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς).
|
|
5
|
+
"version": "3.4.1",
|
|
6
|
+
"description": "Memória cognitiva de longo prazo para o Claude (φρήν + νοῦς). 80 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.4.1",
|
|
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",
|