frinus-mcp 3.2.0 → 3.2.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.
@@ -39,6 +39,7 @@ export declare class MemoryClient implements MemoryClientInterface {
39
39
  metadata?: Record<string, unknown>;
40
40
  context_id?: string;
41
41
  credential_ref?: string;
42
+ universe_id?: string;
42
43
  }): Promise<any>;
43
44
  searchMemories(data: {
44
45
  query_text: string;
@@ -86,10 +86,12 @@ export class MemoryClient {
86
86
  agent_id: data.agent_id,
87
87
  content: data.content,
88
88
  memory_type: data.memory_type || "episodic",
89
- scope: data.scope || "organization",
90
89
  importance: data.importance || 0.5,
91
90
  metadata: data.metadata,
92
91
  };
92
+ // The handler is the source of truth for scope; only send it when defined.
93
+ if (data.scope)
94
+ payload.scope = data.scope;
93
95
  if (data.user_id)
94
96
  payload.user_id = data.user_id;
95
97
  if (data.created_by_user_id)
@@ -98,6 +100,8 @@ export class MemoryClient {
98
100
  payload.context_id = data.context_id;
99
101
  if (data.credential_ref)
100
102
  payload.credential_ref = data.credential_ref;
103
+ if (data.universe_id)
104
+ payload.universe_id = data.universe_id;
101
105
  const response = await this.client.post("/memories", payload);
102
106
  return response.data;
103
107
  }
@@ -38,7 +38,7 @@ Scopes:
38
38
  scope: {
39
39
  type: "string",
40
40
  enum: ["user", "agent", "universe", "organization"],
41
- description: "Visibility scope: user (private), agent (agent-only), universe (department), organization (org-wide). Default: organization",
41
+ description: "Visibility scope. Padrão: a memória é associada ao universo do agente (contexto). 'organization' (org-wide) e 'user' (private) só são usados quando explicitamente definidos; 'agent' (agent-only) é o fallback automático quando não há universo identificável.",
42
42
  },
43
43
  importance: {
44
44
  type: "number",
@@ -56,6 +56,10 @@ Scopes:
56
56
  type: "object",
57
57
  description: "Metadata adicional (tags, universe, idioma, etc.)",
58
58
  },
59
+ universe_id: {
60
+ type: "string",
61
+ description: "UUID do universo/contexto ao qual associar a memória. Se omitido, a memória é associada automaticamente ao universo do agente.",
62
+ },
59
63
  context_id: {
60
64
  type: "string",
61
65
  description: "Optional UUID of a hierarchy node (universe, concept, theme, topic, or point). When provided, the memory is automatically linked to this node and all its ancestors. Takes precedence over universe_id for hierarchy placement.",
@@ -200,39 +200,33 @@ const handlers = {
200
200
  // ==========================================================================
201
201
  // Core Memory
202
202
  // ==========================================================================
203
- async memory_store(args, { memoryClient, resolvedUserId }) {
203
+ async memory_store(args, { memoryClient, cpClient, resolvedUserId }) {
204
+ const createdByUser = args.created_by_user_id || resolvedUserId || undefined;
205
+ const userId = args.user_id || resolvedUserId || undefined;
206
+ const meta = args.metadata || {};
207
+ const universeId = args.universe_id || meta.universe_id || undefined;
208
+ // Context-first default: when the caller does NOT pass an explicit `scope`,
209
+ // memories are associated with a CONTEXT (universe). The backend resolves
210
+ // the universe_id from the agent when none is supplied. `organization` and
211
+ // `user` are only used when explicitly requested by the caller. When the
212
+ // agent has no resolvable universe, we fall back to `agent` scope and help
213
+ // future categorization by returning the list of available universes.
214
+ const explicitScope = args.scope;
215
+ const resolvedScope = explicitScope || "universe";
216
+ const baseInput = {
217
+ agent_id: args.agent_id,
218
+ content: args.content,
219
+ memory_type: args.memory_type,
220
+ importance: args.importance,
221
+ user_id: userId,
222
+ created_by_user_id: createdByUser,
223
+ metadata: args.metadata,
224
+ context_id: args.context_id,
225
+ credential_ref: args.credential_ref,
226
+ universe_id: universeId,
227
+ };
204
228
  try {
205
- const createdByUser = args.created_by_user_id || resolvedUserId || undefined;
206
- const userId = args.user_id || resolvedUserId || undefined;
207
- // Auto-scope inference: if scope not provided, pick the narrowest
208
- // scope justified by the available context. Caller can always pass
209
- // an explicit `scope` to override this heuristic.
210
- const meta = args.metadata || {};
211
- const universeId = args.universe_id || meta.universe_id || undefined;
212
- let resolvedScope = args.scope;
213
- if (!resolvedScope) {
214
- if (userId || createdByUser) {
215
- resolvedScope = "user";
216
- }
217
- else if (universeId) {
218
- resolvedScope = "universe";
219
- }
220
- else {
221
- resolvedScope = "organization";
222
- }
223
- }
224
- const result = await memoryClient.storeMemory({
225
- agent_id: args.agent_id,
226
- content: args.content,
227
- memory_type: args.memory_type,
228
- scope: resolvedScope,
229
- importance: args.importance,
230
- user_id: userId,
231
- created_by_user_id: createdByUser,
232
- metadata: args.metadata,
233
- context_id: args.context_id,
234
- credential_ref: args.credential_ref,
235
- });
229
+ const result = await memoryClient.storeMemory({ ...baseInput, scope: resolvedScope });
236
230
  return {
237
231
  content: [{
238
232
  type: "text",
@@ -241,6 +235,45 @@ const handlers = {
241
235
  };
242
236
  }
243
237
  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
+ }
244
277
  const apiErr = handleApiError(error);
245
278
  if (apiErr)
246
279
  return apiErr;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frinus-mcp",
3
- "version": "3.2.0",
3
+ "version": "3.2.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",