frinus-mcp 3.2.1 → 3.4.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 +121 -71
- package/dist/client/client-info.d.ts +25 -0
- package/dist/client/client-info.js +48 -0
- package/dist/client/cp-client.js +2 -0
- package/dist/client/memory-client.d.ts +15 -0
- package/dist/client/memory-client.js +40 -0
- package/dist/index.js +12 -0
- package/dist/tools/definitions.js +67 -2
- package/dist/tools/handlers.js +233 -62
- package/dist/types/index.d.ts +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,13 +1,32 @@
|
|
|
1
1
|
# Frinus MCP Server
|
|
2
2
|
|
|
3
|
-
> **φρήν + νοῦς** —
|
|
3
|
+
> **φρήν + νοῦς** — Gives your Claude agent long-term, cognitive memory backed by the [Frinus](https://frinus.rdxsec.com.br) platform.
|
|
4
4
|
|
|
5
|
-
MCP (Model Context Protocol) server
|
|
5
|
+
`frinus-mcp` is an MCP (Model Context Protocol) server. It is the **bridge** between your MCP client — Claude Code, Cursor, or Claude Desktop — and the **Frinus platform hosted at https://frinus.rdxsec.com.br**.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
It does **not** run on its own. It stores nothing locally: every tool call is forwarded over HTTPS to your Frinus account. So before it does anything useful you need two things:
|
|
8
|
+
|
|
9
|
+
1. a **Frinus account** (free, no card), and
|
|
10
|
+
2. a **Frinus API key** (`sk-frinus-...`).
|
|
11
|
+
|
|
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
|
+
|
|
14
|
+
- **Version:** 3.3.0
|
|
8
15
|
- **Tools:** 70 (see [Tools Reference](#tools-reference))
|
|
9
|
-
- **
|
|
10
|
-
- **
|
|
16
|
+
- **Platform:** https://frinus.rdxsec.com.br (hosted SaaS — the backend this server talks to)
|
|
17
|
+
- **npm package:** [`frinus-mcp`](https://www.npmjs.com/package/frinus-mcp)
|
|
18
|
+
|
|
19
|
+
## How it fits together
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
+-------------------+ +--------------+ +----------------------------+
|
|
23
|
+
| Your MCP client | stdio | frinus-mcp | HTTPS | Frinus platform |
|
|
24
|
+
| (Claude Code, | <----> | (this server,| <----> | frinus.rdxsec.com.br |
|
|
25
|
+
| Cursor, Desktop) | | via npx) | | (your memories live here) |
|
|
26
|
+
+-------------------+ +--------------+ +----------------------------+
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The server validates your API key against the platform at startup, resolves your account + organisation, and routes every tool call there. No API key → it refuses to start.
|
|
11
30
|
|
|
12
31
|
## Rule Zero — MCP is mandatory
|
|
13
32
|
|
|
@@ -20,70 +39,51 @@ The Frinus MCP **is** the agent's long-term memory, identity, and intelligence.
|
|
|
20
39
|
|
|
21
40
|
The complete protocol set lives in the global `CLAUDE.md` (Frinus organisation), summarised below in [The 7 Protocols](#the-7-protocols).
|
|
22
41
|
|
|
23
|
-
##
|
|
42
|
+
## Before you start — account + API key
|
|
24
43
|
|
|
25
|
-
|
|
26
|
-
- Frinus stack reachable (Memory Engine, Control Plane, Agent Service)
|
|
27
|
-
- Personal API key (`sk-frinus-...`) tied to your tenant
|
|
44
|
+
You need a Frinus account and an API key. Both are free to get.
|
|
28
45
|
|
|
29
|
-
|
|
46
|
+
### 1. Create a free account
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
Sign up at **https://frinus.rdxsec.com.br**. The **Free plan (R$0)** is enough to get started:
|
|
32
49
|
|
|
33
|
-
|
|
50
|
+
- 100 memories
|
|
51
|
+
- 20 queries/day
|
|
52
|
+
- no credit card required
|
|
53
|
+
|
|
54
|
+
### 2. Generate an API key
|
|
55
|
+
|
|
56
|
+
Once logged in, open your account **Settings → API Keys** and create a new key. You'll get a value shaped like:
|
|
34
57
|
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
npm run build
|
|
58
|
+
```
|
|
59
|
+
sk-frinus-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
38
60
|
```
|
|
39
61
|
|
|
40
|
-
|
|
62
|
+
Copy it now — for security it's shown **only once**. This value goes into the `FRINUS_API_KEY` environment variable below. Treat it like a password; never commit it to a repo.
|
|
41
63
|
|
|
42
|
-
##
|
|
64
|
+
## Getting started (zero to first memory)
|
|
43
65
|
|
|
44
|
-
|
|
45
|
-
|---------|--------|-------------|
|
|
46
|
-
| **npm** (`npx -y frinus-mcp`) | Claude Code, Cursor, Codex, opencode | Paste config + API key |
|
|
47
|
-
| **`.mcpb` bundle** | Claude Desktop | Double-click + paste API key |
|
|
48
|
-
| **From source** | any | Clone + build + absolute path |
|
|
66
|
+
Five minutes, four steps.
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
### Step 1 — Create your account
|
|
51
69
|
|
|
52
|
-
|
|
70
|
+
Sign up at https://frinus.rdxsec.com.br (Free plan, no card). See [above](#1-create-a-free-account).
|
|
53
71
|
|
|
54
|
-
|
|
55
|
-
# 1) npm package — powers `npx -y frinus-mcp` everywhere
|
|
56
|
-
npm version <patch|minor|major>
|
|
57
|
-
npm publish # prepublishOnly runs the build
|
|
72
|
+
### Step 2 — Generate your API key
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
npm run pack:mcpb # -> frinus.mcpb
|
|
61
|
-
gh release upload v<version> frinus.mcpb --repo frinus-ai/frinus-mcp --clobber
|
|
62
|
-
# Frontend points at: releases/latest/download/frinus.mcpb
|
|
63
|
-
```
|
|
74
|
+
Settings → API Keys → create. Copy the `sk-frinus-...` value. See [above](#2-generate-an-api-key).
|
|
64
75
|
|
|
65
|
-
|
|
76
|
+
### Step 3 — Add the server to your client
|
|
66
77
|
|
|
67
|
-
|
|
68
|
-
|----------|---------|-------------|
|
|
69
|
-
| `FRINUS_API_KEY` | **required** | Personal API key (`sk-frinus-...`). Resolves tenant + user identity at boot. |
|
|
70
|
-
| `MEMORY_SERVICE_URL` | `http://localhost:8001` | Memory Engine base URL |
|
|
71
|
-
| `FRINUS_CP_URL` | `http://localhost:8000` | Control Plane base URL (universes, orgs, billing) |
|
|
72
|
-
| `AGENT_SERVICE_URL` | `http://localhost:8002` | Agent Service base URL (agents, invocation, skills) |
|
|
73
|
-
| `FRINUS_MEMORY_API_KEY` | — | Legacy fallback for `FRINUS_API_KEY` |
|
|
78
|
+
You don't install anything — `npx` fetches and runs the package on demand. The only value you must provide is your API key; the platform URLs already default to production.
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
**Claude Code (one-liner):**
|
|
76
81
|
|
|
77
82
|
```bash
|
|
78
|
-
|
|
79
|
-
FRINUS_CP_URL=https://frinus-api.rdxsec.com.br
|
|
80
|
-
AGENT_SERVICE_URL=https://frinus-agents.rdxsec.com.br
|
|
81
|
-
FRINUS_API_KEY=sk-frinus-...
|
|
83
|
+
claude mcp add frinus --env FRINUS_API_KEY=sk-frinus-... -- npx -y frinus-mcp@latest
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Add the server to your MCP client configuration. **Recommended (npx, no install):**
|
|
86
|
+
**Cursor / Claude Desktop / any client (config JSON):**
|
|
87
87
|
|
|
88
88
|
```json
|
|
89
89
|
{
|
|
@@ -99,35 +99,45 @@ Add the server to your MCP client configuration. **Recommended (npx, no install)
|
|
|
99
99
|
}
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Config file locations:
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
claude
|
|
104
|
+
- **Claude Code:** `~/.claude.json`, under `mcpServers`.
|
|
105
|
+
- **Claude Desktop:** `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) / `~/.config/claude/claude_desktop_config.json` (Linux). Or use the one-click `.mcpb` extension and just paste your API key.
|
|
106
|
+
- **Cursor:** `~/.cursor/mcp.json`, under `mcpServers`.
|
|
107
|
+
|
|
108
|
+
Restart the client so it picks up the new server.
|
|
109
|
+
|
|
110
|
+
> Replace `sk-frinus-...` with the real key from Step 2. You normally set **only** `FRINUS_API_KEY` — every backend URL already defaults to the hosted platform. See [Environment Variables](#environment-variables) for the full list and the self-host override.
|
|
111
|
+
|
|
112
|
+
### Step 4 — Validate with your first memory
|
|
113
|
+
|
|
114
|
+
In your client, ask the agent to store and recall something:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
Store this in Frinus memory: "Our staging DB is reset every night at 02:00 UTC."
|
|
106
118
|
```
|
|
107
119
|
|
|
108
|
-
|
|
120
|
+
then in the same or a later session:
|
|
109
121
|
|
|
110
|
-
```json
|
|
111
|
-
{
|
|
112
|
-
"mcpServers": {
|
|
113
|
-
"frinus": {
|
|
114
|
-
"command": "node",
|
|
115
|
-
"args": ["/absolute/path/to/mcp/dist/index.js"],
|
|
116
|
-
"env": {
|
|
117
|
-
"FRINUS_API_KEY": "sk-frinus-...",
|
|
118
|
-
"MEMORY_SERVICE_URL": "http://localhost:8001",
|
|
119
|
-
"FRINUS_CP_URL": "http://localhost:8000",
|
|
120
|
-
"AGENT_SERVICE_URL": "http://localhost:8002"
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
122
|
```
|
|
123
|
+
What time does our staging DB reset?
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Behind the scenes the agent calls `memory_store` then `memory_search` / `search_with_attention`. If the recall comes back, the bridge is live. You can confirm the same memory appears in the web app at https://frinus.rdxsec.com.br.
|
|
126
127
|
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
## Environment Variables
|
|
129
|
+
|
|
130
|
+
In normal use you set **only** `FRINUS_API_KEY`. The service URLs already point at the hosted platform — leave them alone unless you self-host (see [Advanced — self-host](#advanced--self-host--local-dev)).
|
|
131
|
+
|
|
132
|
+
| Variable | Default (hosted platform) | Description |
|
|
133
|
+
|----------|---------------------------|-------------|
|
|
134
|
+
| `FRINUS_API_KEY` | **required** | Your personal API key (`sk-frinus-...`). Resolves account + organisation at boot. |
|
|
135
|
+
| `MEMORY_SERVICE_URL` | `https://frinus-memory.rdxsec.com.br` | Memory Engine base URL |
|
|
136
|
+
| `FRINUS_CP_URL` | `https://frinus-api.rdxsec.com.br` | Control Plane base URL (account, orgs, API keys, billing) |
|
|
137
|
+
| `AGENT_SERVICE_URL` | `https://frinus-agents.rdxsec.com.br` | Agent Service base URL (agents, invocation, skills) |
|
|
138
|
+
| `FRINUS_MEMORY_API_KEY` | — | Legacy fallback for `FRINUS_API_KEY` |
|
|
129
139
|
|
|
130
|
-
On startup the server validates the API key
|
|
140
|
+
On startup the server validates the API key against the platform and resolves your organisation + logged-in user. If validation fails it aborts with `[FATAL]` so the client surfaces the error — re-check the key from Settings → API Keys.
|
|
131
141
|
|
|
132
142
|
## The 7 Protocols
|
|
133
143
|
|
|
@@ -409,6 +419,46 @@ Operational guidelines:
|
|
|
409
419
|
|
|
410
420
|
Tenant isolation is database-per-tenant. The MCP resolves your tenant org ID from the API key at boot — you never pass `org_id` manually.
|
|
411
421
|
|
|
422
|
+
## Advanced — self-host / local dev
|
|
423
|
+
|
|
424
|
+
Everything above targets the hosted platform at https://frinus.rdxsec.com.br, which is what almost everyone wants. If you run your own Frinus stack (or develop the MCP against a local backend), build from source and override the three URLs:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
git clone https://github.com/frinus-ai/frinus-mcp && cd frinus-mcp
|
|
428
|
+
npm install
|
|
429
|
+
npm run build # emits dist/index.js
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{
|
|
434
|
+
"mcpServers": {
|
|
435
|
+
"frinus": {
|
|
436
|
+
"command": "node",
|
|
437
|
+
"args": ["/absolute/path/to/mcp/dist/index.js"],
|
|
438
|
+
"env": {
|
|
439
|
+
"FRINUS_API_KEY": "sk-frinus-...",
|
|
440
|
+
"MEMORY_SERVICE_URL": "http://localhost:8001",
|
|
441
|
+
"FRINUS_CP_URL": "http://localhost:8000",
|
|
442
|
+
"AGENT_SERVICE_URL": "http://localhost:8002"
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
### Publishing (maintainers)
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# 1) npm package — powers `npx -y frinus-mcp` everywhere
|
|
453
|
+
npm version <patch|minor|major>
|
|
454
|
+
npm publish # prepublishOnly runs the build
|
|
455
|
+
|
|
456
|
+
# 2) .mcpb bundle — powers the Claude Desktop one-click install
|
|
457
|
+
npm run pack:mcpb # -> frinus.mcpb
|
|
458
|
+
gh release upload v<version> frinus.mcpb --repo frinus-ai/frinus-mcp --clobber
|
|
459
|
+
# Frontend points at: releases/latest/download/frinus.mcpb
|
|
460
|
+
```
|
|
461
|
+
|
|
412
462
|
## Development
|
|
413
463
|
|
|
414
464
|
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved MCP client identity.
|
|
3
|
+
*
|
|
4
|
+
* The MCP client sends its `clientInfo: { name, version }` during the
|
|
5
|
+
* `initialize` handshake. We normalize the raw `name` into a small, stable
|
|
6
|
+
* set of buckets and expose it as the `X-Client` header so the Control Plane
|
|
7
|
+
* can attribute the `mcp_connected` PostHog event to the right client
|
|
8
|
+
* ("claude_code", "cursor", "desktop", "codex", "unknown").
|
|
9
|
+
*
|
|
10
|
+
* Module-level singleton (getter/setter), mirroring `resolvedTenantOrgId` in
|
|
11
|
+
* memory-client.ts: it is captured by an Axios request interceptor at
|
|
12
|
+
* construction time and read lazily on each request, so a single mutable
|
|
13
|
+
* module value is the least-surprising shape here.
|
|
14
|
+
*/
|
|
15
|
+
export type ResolvedClient = "claude_code" | "cursor" | "desktop" | "codex" | "unknown";
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a raw `clientInfo.name` into a stable bucket.
|
|
18
|
+
*
|
|
19
|
+
* Order matters: the more specific "claude-code" check must run before the
|
|
20
|
+
* generic "claude" → desktop fallback, so Claude Code is never misclassified
|
|
21
|
+
* as the desktop app.
|
|
22
|
+
*/
|
|
23
|
+
export declare function normalizeClient(rawName?: string | null): ResolvedClient;
|
|
24
|
+
export declare function setResolvedClient(rawName?: string | null): void;
|
|
25
|
+
export declare function getResolvedClient(): ResolvedClient;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolved MCP client identity.
|
|
3
|
+
*
|
|
4
|
+
* The MCP client sends its `clientInfo: { name, version }` during the
|
|
5
|
+
* `initialize` handshake. We normalize the raw `name` into a small, stable
|
|
6
|
+
* set of buckets and expose it as the `X-Client` header so the Control Plane
|
|
7
|
+
* can attribute the `mcp_connected` PostHog event to the right client
|
|
8
|
+
* ("claude_code", "cursor", "desktop", "codex", "unknown").
|
|
9
|
+
*
|
|
10
|
+
* Module-level singleton (getter/setter), mirroring `resolvedTenantOrgId` in
|
|
11
|
+
* memory-client.ts: it is captured by an Axios request interceptor at
|
|
12
|
+
* construction time and read lazily on each request, so a single mutable
|
|
13
|
+
* module value is the least-surprising shape here.
|
|
14
|
+
*/
|
|
15
|
+
let resolvedClient = "unknown";
|
|
16
|
+
/**
|
|
17
|
+
* Normalize a raw `clientInfo.name` into a stable bucket.
|
|
18
|
+
*
|
|
19
|
+
* Order matters: the more specific "claude-code" check must run before the
|
|
20
|
+
* generic "claude" → desktop fallback, so Claude Code is never misclassified
|
|
21
|
+
* as the desktop app.
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeClient(rawName) {
|
|
24
|
+
if (!rawName)
|
|
25
|
+
return "unknown";
|
|
26
|
+
const name = rawName.toLowerCase();
|
|
27
|
+
if (name.includes("claude-code") || name.includes("claudecode")) {
|
|
28
|
+
return "claude_code";
|
|
29
|
+
}
|
|
30
|
+
if (name.includes("cursor")) {
|
|
31
|
+
return "cursor";
|
|
32
|
+
}
|
|
33
|
+
if (name.includes("codex")) {
|
|
34
|
+
return "codex";
|
|
35
|
+
}
|
|
36
|
+
// Generic Claude clients (Claude Desktop, claude-ai, etc.) — must come after
|
|
37
|
+
// the claude-code check above.
|
|
38
|
+
if (name.includes("claude") || name.includes("claude-ai") || name.includes("desktop")) {
|
|
39
|
+
return "desktop";
|
|
40
|
+
}
|
|
41
|
+
return "unknown";
|
|
42
|
+
}
|
|
43
|
+
export function setResolvedClient(rawName) {
|
|
44
|
+
resolvedClient = normalizeClient(rawName);
|
|
45
|
+
}
|
|
46
|
+
export function getResolvedClient() {
|
|
47
|
+
return resolvedClient;
|
|
48
|
+
}
|
package/dist/client/cp-client.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import axios from "axios";
|
|
5
5
|
import { getResolvedTenantOrgId } from "./memory-client.js";
|
|
6
|
+
import { getResolvedClient } from "./client-info.js";
|
|
6
7
|
export class CpClient {
|
|
7
8
|
client;
|
|
8
9
|
constructor(baseURL, apiKey) {
|
|
@@ -16,6 +17,7 @@ export class CpClient {
|
|
|
16
17
|
const orgId = getResolvedTenantOrgId();
|
|
17
18
|
if (orgId)
|
|
18
19
|
config.headers['X-Tenant-ID'] = orgId;
|
|
20
|
+
config.headers['X-Client'] = getResolvedClient();
|
|
19
21
|
return config;
|
|
20
22
|
});
|
|
21
23
|
}
|
|
@@ -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) {
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextpro
|
|
|
17
17
|
import { MemoryClient, getResolvedUserEmail, getResolvedUserId, setResolvedTenantOrgId, setResolvedUserEmail, setResolvedUserId, getResolvedTenantOrgId, } from "./client/memory-client.js";
|
|
18
18
|
import { AgentClient } from "./client/agent-client.js";
|
|
19
19
|
import { CpClient } from "./client/cp-client.js";
|
|
20
|
+
import { getResolvedClient, setResolvedClient } from "./client/client-info.js";
|
|
20
21
|
import { InteractionCapture } from "./capture/interaction-capture.js";
|
|
21
22
|
import { TOOLS } from "./tools/definitions.js";
|
|
22
23
|
import { dispatchTool } from "./tools/handlers.js";
|
|
@@ -95,6 +96,17 @@ async function main() {
|
|
|
95
96
|
}
|
|
96
97
|
return toolResult;
|
|
97
98
|
});
|
|
99
|
+
// Resolve the connecting MCP client's identity for the X-Client header.
|
|
100
|
+
// `getClientVersion()` is only populated after the `initialize` handshake,
|
|
101
|
+
// so we read it from the `oninitialized` callback (fired on the client's
|
|
102
|
+
// `notifications/initialized`), not right after `connect()`.
|
|
103
|
+
server.oninitialized = () => {
|
|
104
|
+
const rawName = server.getClientVersion()?.name;
|
|
105
|
+
setResolvedClient(rawName);
|
|
106
|
+
// Logged once for calibration against real clients (stderr, not stdout —
|
|
107
|
+
// stdout is the MCP transport).
|
|
108
|
+
console.error(`[Client] clientInfo.name=${JSON.stringify(rawName ?? null)} normalized=${getResolvedClient()}`);
|
|
109
|
+
};
|
|
98
110
|
// Start server
|
|
99
111
|
const transport = new StdioServerTransport();
|
|
100
112
|
await server.connect(transport);
|
|
@@ -9,6 +9,13 @@ 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") of which universe/context node it belongs to —
|
|
14
|
+
nothing is moved automatically. Review pending suggestions with
|
|
15
|
+
memory_classification_pending and apply them with memory_classification_approve.
|
|
16
|
+
Passing scope/universe_id/context_id explicitly sets the destination directly
|
|
17
|
+
(no suggestion needed).
|
|
18
|
+
|
|
12
19
|
Memory types:
|
|
13
20
|
- episodic: Specific experiences and events (what happened)
|
|
14
21
|
- semantic: General knowledge and facts (what I know)
|
|
@@ -38,7 +45,7 @@ Scopes:
|
|
|
38
45
|
scope: {
|
|
39
46
|
type: "string",
|
|
40
47
|
enum: ["user", "agent", "universe", "organization"],
|
|
41
|
-
description: "
|
|
48
|
+
description: "Optional explicit visibility scope. Omit to let the backend auto-classify and return a suggestion (status 'proposed'). When set, it overrides classification (status 'manual'): 'organization' (org-wide), 'user' (private), 'universe' (department-shared), 'agent' (agent-only).",
|
|
42
49
|
},
|
|
43
50
|
importance: {
|
|
44
51
|
type: "number",
|
|
@@ -58,7 +65,7 @@ Scopes:
|
|
|
58
65
|
},
|
|
59
66
|
universe_id: {
|
|
60
67
|
type: "string",
|
|
61
|
-
description: "UUID
|
|
68
|
+
description: "Optional UUID of a universe to associate the memory with explicitly (overrides auto-classification → status 'manual'). Omit to let the backend suggest one.",
|
|
62
69
|
},
|
|
63
70
|
context_id: {
|
|
64
71
|
type: "string",
|
|
@@ -156,6 +163,64 @@ Use with caution - this permanently removes the memory.`,
|
|
|
156
163
|
},
|
|
157
164
|
},
|
|
158
165
|
// ==========================================================================
|
|
166
|
+
// Auto-classification — human-in-the-loop (3)
|
|
167
|
+
// ==========================================================================
|
|
168
|
+
{
|
|
169
|
+
name: "memory_classification_pending",
|
|
170
|
+
description: `List memories whose auto-classification is awaiting your approval.
|
|
171
|
+
|
|
172
|
+
The backend classifies each new memory by content and proposes a target
|
|
173
|
+
universe/context node (status "proposed"). This returns that review queue with,
|
|
174
|
+
per item: the memory id, the suggested destination breadcrumb, and the
|
|
175
|
+
confidence. Approve with memory_classification_approve, or correct the target by
|
|
176
|
+
passing universe_id/context_id to it.`,
|
|
177
|
+
inputSchema: {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties: {
|
|
180
|
+
limit: { type: "integer", description: "Maximum suggestions to return (default: backend default)" },
|
|
181
|
+
},
|
|
182
|
+
required: [],
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "memory_classification_approve",
|
|
187
|
+
description: `Approve a proposed classification, moving the memory to its suggested
|
|
188
|
+
universe/context node.
|
|
189
|
+
|
|
190
|
+
Without overrides, the backend applies its own suggestion. Pass universe_id
|
|
191
|
+
and/or context_id to override the suggested target (also the way to CORRECT a
|
|
192
|
+
wrong suggestion).`,
|
|
193
|
+
inputSchema: {
|
|
194
|
+
type: "object",
|
|
195
|
+
properties: {
|
|
196
|
+
memory_id: { type: "string", description: "UUID of the memory whose classification is being approved" },
|
|
197
|
+
universe_id: { type: "string", description: "Optional UUID of a universe to override the suggested destination" },
|
|
198
|
+
context_id: { type: "string", description: "Optional UUID of a hierarchy node (concept/theme/topic/point) to override the suggested destination" },
|
|
199
|
+
},
|
|
200
|
+
required: ["memory_id"],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "memory_reclassify",
|
|
205
|
+
description: `Re-classify a memory. Either pin it to an explicit destination, or ask the
|
|
206
|
+
backend to re-run its automatic classifier.
|
|
207
|
+
|
|
208
|
+
- Pass universe_id and/or context_id to set the destination directly (correction).
|
|
209
|
+
- Pass rerun_auto=true (and no explicit target) to discard the current
|
|
210
|
+
suggestion and let the backend propose a new one (review via
|
|
211
|
+
memory_classification_pending).`,
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
memory_id: { type: "string", description: "UUID of the memory to reclassify" },
|
|
216
|
+
universe_id: { type: "string", description: "Optional UUID of a universe to set as the explicit destination" },
|
|
217
|
+
context_id: { type: "string", description: "Optional UUID of a hierarchy node to set as the explicit destination" },
|
|
218
|
+
rerun_auto: { type: "boolean", description: "When true (and no explicit target), re-run the backend's automatic classification" },
|
|
219
|
+
},
|
|
220
|
+
required: ["memory_id"],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
// ==========================================================================
|
|
159
224
|
// Dynamic Relevance (2)
|
|
160
225
|
// ==========================================================================
|
|
161
226
|
{
|
package/dist/tools/handlers.js
CHANGED
|
@@ -196,84 +196,255 @@ 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
|
+
* Render the memory_store response. New backend: shows the auto-classification
|
|
273
|
+
* suggestion (proposed) or final destination (manual/approved) enxuto, no
|
|
274
|
+
* intrusive "Dica/Universos disponíveis" block. Old backend (no classification
|
|
275
|
+
* fields): minimal ID/Type/Scope, also WITHOUT any hint.
|
|
276
|
+
*/
|
|
277
|
+
function renderStoreResult(result) {
|
|
278
|
+
const id = result?.id;
|
|
279
|
+
const status = result?.classification_status;
|
|
280
|
+
const head = `✓ Salva`;
|
|
281
|
+
// Defensive: older backend without classification fields.
|
|
282
|
+
if (!status) {
|
|
283
|
+
return `Memory stored successfully.\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}`;
|
|
284
|
+
}
|
|
285
|
+
if (status === "proposed") {
|
|
286
|
+
const uni = universeName(result?.classified_universe);
|
|
287
|
+
const ctx = contextName(result?.classified_context);
|
|
288
|
+
const conf = confidenceLabel(result?.confidence);
|
|
289
|
+
// High/medium-confidence node identified.
|
|
290
|
+
if (uni && ctx) {
|
|
291
|
+
const confTxt = conf ? `, confiança ${conf}` : "";
|
|
292
|
+
return `${head} · sugestão: ${suggestionLabel(result)} (auto${confTxt}) — aprove com memory_classification_approve`;
|
|
293
|
+
}
|
|
294
|
+
// Universe-only suggestion (no specific node).
|
|
295
|
+
if (uni) {
|
|
296
|
+
return `${head} · sugestão de universo: ${uni} (nó específico não identificado) — aprove com memory_classification_approve`;
|
|
297
|
+
}
|
|
298
|
+
// Low / no confidence: no reliable suggestion.
|
|
299
|
+
return `${head} · sem sugestão confiável — use memory_classification_pending para revisar`;
|
|
300
|
+
}
|
|
301
|
+
// manual / approved: show final destination without "sugestão".
|
|
302
|
+
const dest = destinationLabel(result);
|
|
303
|
+
if (dest)
|
|
304
|
+
return `${head} · classificada em: ${dest}`;
|
|
305
|
+
return `${head}\nID: ${id}\nType: ${result?.memory_type}\nScope: ${result?.scope}`;
|
|
306
|
+
}
|
|
199
307
|
const handlers = {
|
|
200
308
|
// ==========================================================================
|
|
201
309
|
// Core Memory
|
|
202
310
|
// ==========================================================================
|
|
203
|
-
async memory_store(args, { memoryClient,
|
|
311
|
+
async memory_store(args, { memoryClient, resolvedUserId }) {
|
|
204
312
|
const createdByUser = args.created_by_user_id || resolvedUserId || undefined;
|
|
205
313
|
const userId = args.user_id || resolvedUserId || undefined;
|
|
206
314
|
const meta = args.metadata || {};
|
|
207
315
|
const universeId = args.universe_id || meta.universe_id || undefined;
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
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
|
-
};
|
|
316
|
+
// Human-in-the-loop classification: the backend classifies the memory by
|
|
317
|
+
// content at write time and returns a SUGGESTION (classification_status
|
|
318
|
+
// "proposed"). Nothing is moved automatically — the user reviews via
|
|
319
|
+
// memory_classification_pending and approves with
|
|
320
|
+
// memory_classification_approve. The MCP layer no longer injects an
|
|
321
|
+
// implicit "universe" scope nor surfaces "available universes"; scope,
|
|
322
|
+
// universe_id and context_id are forwarded ONLY when the caller sets them
|
|
323
|
+
// (explicit overrides → classification_status "manual").
|
|
228
324
|
try {
|
|
229
|
-
const result = await memoryClient.storeMemory({
|
|
325
|
+
const result = await memoryClient.storeMemory({
|
|
326
|
+
agent_id: args.agent_id,
|
|
327
|
+
content: args.content,
|
|
328
|
+
memory_type: args.memory_type,
|
|
329
|
+
importance: args.importance,
|
|
330
|
+
user_id: userId,
|
|
331
|
+
created_by_user_id: createdByUser,
|
|
332
|
+
metadata: args.metadata,
|
|
333
|
+
context_id: args.context_id,
|
|
334
|
+
credential_ref: args.credential_ref,
|
|
335
|
+
universe_id: universeId,
|
|
336
|
+
scope: args.scope,
|
|
337
|
+
});
|
|
338
|
+
return { content: [{ type: "text", text: renderStoreResult(result) }] };
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
const apiErr = handleApiError(error);
|
|
342
|
+
if (apiErr)
|
|
343
|
+
return apiErr;
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
// ==========================================================================
|
|
348
|
+
// Auto-classification (human-in-the-loop)
|
|
349
|
+
// ==========================================================================
|
|
350
|
+
async memory_classification_pending(args, { memoryClient }) {
|
|
351
|
+
let result;
|
|
352
|
+
try {
|
|
353
|
+
result = await memoryClient.getPendingClassifications(args.limit);
|
|
354
|
+
}
|
|
355
|
+
catch (error) {
|
|
356
|
+
const apiErr = handleApiError(error);
|
|
357
|
+
if (apiErr)
|
|
358
|
+
return apiErr;
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
const items = Array.isArray(result) ? result : (result?.items || result?.pending || []);
|
|
362
|
+
if (!items || items.length === 0) {
|
|
363
|
+
return { content: [{ type: "text", text: "Nenhuma sugestão de classificação aguardando aprovação." }] };
|
|
364
|
+
}
|
|
365
|
+
let formatted = `Sugestões aguardando aprovação (${items.length}):\n\n`;
|
|
366
|
+
items.forEach((m, i) => {
|
|
367
|
+
const label = suggestionLabel(m);
|
|
368
|
+
const conf = confidenceLabel(m.confidence);
|
|
369
|
+
const preview = typeof m.content === "string"
|
|
370
|
+
? (m.content.length > 120 ? m.content.slice(0, 120) + "…" : m.content)
|
|
371
|
+
: "";
|
|
372
|
+
formatted += `${i + 1}. (id: ${m.id})`;
|
|
373
|
+
if (conf)
|
|
374
|
+
formatted += ` · confiança ${conf}`;
|
|
375
|
+
formatted += `\n sugestão: ${label}\n`;
|
|
376
|
+
if (preview)
|
|
377
|
+
formatted += ` ${preview}\n`;
|
|
378
|
+
formatted += `\n`;
|
|
379
|
+
});
|
|
380
|
+
formatted += `Aprove com memory_classification_approve(memory_id) — passe universe_id/context_id para corrigir o destino.`;
|
|
381
|
+
return { content: [{ type: "text", text: formatted }] };
|
|
382
|
+
},
|
|
383
|
+
async memory_classification_approve(args, { memoryClient }) {
|
|
384
|
+
const memoryId = args.memory_id;
|
|
385
|
+
const override = {};
|
|
386
|
+
if (args.universe_id)
|
|
387
|
+
override.universe_id = args.universe_id;
|
|
388
|
+
if (args.context_id)
|
|
389
|
+
override.context_id = args.context_id;
|
|
390
|
+
let result;
|
|
391
|
+
try {
|
|
392
|
+
result = await memoryClient.approveClassification(memoryId, override);
|
|
393
|
+
}
|
|
394
|
+
catch (error) {
|
|
395
|
+
const apiErr = handleApiError(error);
|
|
396
|
+
if (apiErr)
|
|
397
|
+
return apiErr;
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
const dest = destinationLabel(result) || suggestionLabel(result);
|
|
401
|
+
const overridden = Boolean(override.universe_id || override.context_id);
|
|
402
|
+
let text = `✓ Classificação aprovada`;
|
|
403
|
+
if (dest)
|
|
404
|
+
text += ` · ${dest}`;
|
|
405
|
+
if (overridden)
|
|
406
|
+
text += ` (destino corrigido pelo usuário)`;
|
|
407
|
+
text += `\nID: ${memoryId}`;
|
|
408
|
+
return { content: [{ type: "text", text }] };
|
|
409
|
+
},
|
|
410
|
+
async memory_reclassify(args, { memoryClient }) {
|
|
411
|
+
const memoryId = args.memory_id;
|
|
412
|
+
const rerunAuto = args.rerun_auto;
|
|
413
|
+
const universeId = args.universe_id;
|
|
414
|
+
const contextId = args.context_id;
|
|
415
|
+
try {
|
|
416
|
+
// Correction = approve with an override (or reject to re-run the
|
|
417
|
+
// backend's auto-classifier when no explicit target is given).
|
|
418
|
+
if (universeId || contextId) {
|
|
419
|
+
const result = await memoryClient.approveClassification(memoryId, {
|
|
420
|
+
universe_id: universeId,
|
|
421
|
+
context_id: contextId,
|
|
422
|
+
});
|
|
423
|
+
const dest = destinationLabel(result) || suggestionLabel(result);
|
|
424
|
+
let text = `✓ Reclassificada`;
|
|
425
|
+
if (dest)
|
|
426
|
+
text += ` · ${dest}`;
|
|
427
|
+
text += ` (destino definido pelo usuário)\nID: ${memoryId}`;
|
|
428
|
+
return { content: [{ type: "text", text }] };
|
|
429
|
+
}
|
|
430
|
+
if (rerunAuto) {
|
|
431
|
+
const result = await memoryClient.rejectClassification(memoryId);
|
|
432
|
+
const dest = suggestionLabel(result);
|
|
433
|
+
let text = `✓ Sugestão anterior descartada; reclassificação automática solicitada`;
|
|
434
|
+
if (dest)
|
|
435
|
+
text += ` · nova sugestão: ${dest}`;
|
|
436
|
+
text += `\nID: ${memoryId}\nRevise em memory_classification_pending.`;
|
|
437
|
+
return { content: [{ type: "text", text }] };
|
|
438
|
+
}
|
|
230
439
|
return {
|
|
231
440
|
content: [{
|
|
232
441
|
type: "text",
|
|
233
|
-
text:
|
|
442
|
+
text: "memory_reclassify requer universe_id/context_id (destino corrigido) ou rerun_auto=true (reexecutar classificação automática).",
|
|
234
443
|
}],
|
|
444
|
+
isError: true,
|
|
235
445
|
};
|
|
236
446
|
}
|
|
237
447
|
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
448
|
const apiErr = handleApiError(error);
|
|
278
449
|
if (apiErr)
|
|
279
450
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "frinus-mcp",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.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",
|