@cybermem/mcp 0.9.12 → 0.13.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/Dockerfile +15 -18
- package/dist/console-fix.js +14 -0
- package/dist/env.js +14 -4
- package/dist/index.js +168 -326
- package/e2e/api.spec.ts +259 -0
- package/package.json +6 -4
- package/playwright.config.ts +17 -0
- package/src/console-fix.ts +13 -0
- package/src/env.ts +21 -4
- package/src/index.ts +202 -407
- package/src/openmemory-js.d.ts +25 -0
- package/requirements.txt +0 -2
- package/server.py +0 -347
- package/test_mcp.py +0 -111
package/dist/index.js
CHANGED
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
"use strict";
|
|
3
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
4
|
};
|
|
6
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
require("./console-fix.js");
|
|
7
7
|
require("./env.js");
|
|
8
|
-
/**
|
|
9
|
-
* CyberMem MCP Server
|
|
10
|
-
*
|
|
11
|
-
* Supports two modes:
|
|
12
|
-
* 1. Local/Server Mode (default): Uses openmemory-js SDK directly.
|
|
13
|
-
* 2. Remote Client Mode (with --url): Proxies requests to a remote CyberMem server via HTTP.
|
|
14
|
-
*/
|
|
15
8
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
16
9
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
17
10
|
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -21,19 +14,6 @@ const axios_1 = __importDefault(require("axios"));
|
|
|
21
14
|
const cors_1 = __importDefault(require("cors"));
|
|
22
15
|
const express_1 = __importDefault(require("express"));
|
|
23
16
|
const zod_1 = require("zod");
|
|
24
|
-
// Redirect all stdout to stderr IMMEDIATELY to protect Stdio protocol
|
|
25
|
-
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
26
|
-
process.stdout.write = (chunk, encoding, callback) => {
|
|
27
|
-
const str = typeof chunk === "string" ? chunk : chunk.toString();
|
|
28
|
-
// Allow ONLY protocol messages (must be JSON-RPC)
|
|
29
|
-
if (str.includes('"jsonrpc":')) {
|
|
30
|
-
return originalStdoutWrite(chunk, encoding, callback);
|
|
31
|
-
}
|
|
32
|
-
return process.stderr.write(chunk, encoding, callback);
|
|
33
|
-
};
|
|
34
|
-
// Also redirect console outputs
|
|
35
|
-
console.log = console.error;
|
|
36
|
-
console.info = console.error;
|
|
37
17
|
// Async Storage for Request Context (User ID and Client Name)
|
|
38
18
|
const requestContext = new async_hooks_1.AsyncLocalStorage();
|
|
39
19
|
// CLI args processing
|
|
@@ -47,15 +27,22 @@ async function startServer() {
|
|
|
47
27
|
};
|
|
48
28
|
const cliUrl = getArg("--url");
|
|
49
29
|
const cliToken = getArg("--token") || getArg("--api-key");
|
|
30
|
+
const cliEnv = getArg("--env");
|
|
31
|
+
if (cliEnv === "staging") {
|
|
32
|
+
console.error("[MCP] Running in Staging environment");
|
|
33
|
+
process.env.CYBERMEM_ENV = "staging";
|
|
34
|
+
}
|
|
50
35
|
let stdioClientName = undefined;
|
|
51
36
|
// Protocol Instructions
|
|
52
37
|
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
53
38
|
PROTOCOL:
|
|
54
39
|
1. On session start: call query_memory("user context profile")
|
|
55
|
-
2. Store new insights immediately with add_memory (
|
|
56
|
-
3.
|
|
40
|
+
2. Store new insights immediately with add_memory (STABLE data)
|
|
41
|
+
3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
|
|
42
|
+
4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
|
|
43
|
+
5. Always include tags: [topic, year, source:your-client-name]
|
|
57
44
|
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
58
|
-
const server = new mcp_js_1.McpServer({ name: "cybermem", version: "0.
|
|
45
|
+
const server = new mcp_js_1.McpServer({ name: "cybermem", version: "0.12.4" }, {
|
|
59
46
|
instructions: CYBERMEM_INSTRUCTIONS,
|
|
60
47
|
});
|
|
61
48
|
server.registerResource("CyberMem Agent Protocol", "cybermem://protocol", { description: "Instructions for AI agents", mimeType: "text/plain" }, async () => ({
|
|
@@ -80,13 +67,15 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
80
67
|
},
|
|
81
68
|
serverInfo: {
|
|
82
69
|
name: "cybermem",
|
|
83
|
-
version: "0.
|
|
70
|
+
version: "0.12.4",
|
|
84
71
|
},
|
|
85
72
|
};
|
|
86
73
|
});
|
|
87
74
|
// --- IMPLEMENTATION LOGIC ---
|
|
88
75
|
let memory = null;
|
|
89
76
|
let apiClient = null;
|
|
77
|
+
let sdk_update_memory = null;
|
|
78
|
+
let sdk_reinforce_memory = null;
|
|
90
79
|
if (cliUrl) {
|
|
91
80
|
// REMOTE CLIENT MODE
|
|
92
81
|
console.error(`Connecting to remote CyberMem at ${cliUrl}`);
|
|
@@ -98,23 +87,18 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
98
87
|
"Content-Type": "application/json",
|
|
99
88
|
},
|
|
100
89
|
});
|
|
101
|
-
// Dynamically inject client name from context or discovery
|
|
102
90
|
apiClient.interceptors.request.use((config) => {
|
|
103
91
|
const ctx = requestContext.getStore();
|
|
104
92
|
config.headers["X-Client-Name"] =
|
|
105
|
-
ctx?.clientName || stdioClientName || "
|
|
93
|
+
ctx?.clientName || stdioClientName || "antigravity-client";
|
|
106
94
|
return config;
|
|
107
95
|
});
|
|
108
96
|
}
|
|
109
97
|
else {
|
|
110
98
|
// LOCAL SDK MODE
|
|
111
|
-
const
|
|
112
|
-
// FORCE absolute standardized path for consistency across components
|
|
113
|
-
const path = await import("path");
|
|
114
|
-
const dbPath = path.resolve(homedir, ".cybermem/data/openmemory.sqlite");
|
|
115
|
-
process.env.OM_DB_PATH = dbPath;
|
|
116
|
-
// Ensure directory exists
|
|
99
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
117
100
|
const fs = await import("fs");
|
|
101
|
+
const path = await import("path");
|
|
118
102
|
try {
|
|
119
103
|
const dir = path.dirname(dbPath);
|
|
120
104
|
if (dir)
|
|
@@ -122,60 +106,20 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
122
106
|
}
|
|
123
107
|
catch { }
|
|
124
108
|
try {
|
|
125
|
-
// Dynamic import to ensure env vars are set before loading SDK
|
|
126
|
-
// We import from dist/core/memory directly to avoid triggering the server side-effects in openmemory-js/dist/index.js
|
|
127
|
-
// @ts-ignore
|
|
128
109
|
const { Memory } = await import("openmemory-js/dist/core/memory.js");
|
|
110
|
+
const hsg = await import("openmemory-js/dist/memory/hsg.js");
|
|
111
|
+
sdk_update_memory = hsg.update_memory;
|
|
112
|
+
sdk_reinforce_memory = hsg.reinforce_memory;
|
|
129
113
|
memory = new Memory();
|
|
130
114
|
server._memoryReady = true;
|
|
131
|
-
//
|
|
115
|
+
// Initialize Tables
|
|
132
116
|
const sqlite3 = await import("sqlite3");
|
|
133
117
|
const db = new sqlite3.default.Database(dbPath);
|
|
134
118
|
db.configure("busyTimeout", 5000);
|
|
135
119
|
db.serialize(() => {
|
|
136
|
-
db.run("
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
});
|
|
140
|
-
db.run(`CREATE TABLE IF NOT EXISTS cybermem_stats (
|
|
141
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
142
|
-
client_name TEXT NOT NULL,
|
|
143
|
-
operation TEXT NOT NULL,
|
|
144
|
-
count INTEGER DEFAULT 0,
|
|
145
|
-
errors INTEGER DEFAULT 0,
|
|
146
|
-
last_updated INTEGER NOT NULL,
|
|
147
|
-
UNIQUE(client_name, operation)
|
|
148
|
-
);`, (err) => {
|
|
149
|
-
if (err)
|
|
150
|
-
console.error("[MCP] Init stats table error:", err.message);
|
|
151
|
-
});
|
|
152
|
-
db.run(`CREATE TABLE IF NOT EXISTS cybermem_access_log (
|
|
153
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
154
|
-
timestamp INTEGER NOT NULL,
|
|
155
|
-
client_name TEXT NOT NULL,
|
|
156
|
-
client_version TEXT,
|
|
157
|
-
method TEXT NOT NULL,
|
|
158
|
-
endpoint TEXT NOT NULL,
|
|
159
|
-
operation TEXT NOT NULL,
|
|
160
|
-
status TEXT NOT NULL,
|
|
161
|
-
is_error INTEGER DEFAULT 0
|
|
162
|
-
);`, (err) => {
|
|
163
|
-
if (err)
|
|
164
|
-
console.error("[MCP] Init access_log table error:", err.message);
|
|
165
|
-
});
|
|
166
|
-
// Access keys table for token-based auth
|
|
167
|
-
db.run(`CREATE TABLE IF NOT EXISTS access_keys (
|
|
168
|
-
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
|
|
169
|
-
key_hash TEXT NOT NULL,
|
|
170
|
-
name TEXT DEFAULT 'default',
|
|
171
|
-
user_id TEXT DEFAULT 'default',
|
|
172
|
-
created_at TEXT DEFAULT (datetime('now')),
|
|
173
|
-
last_used_at TEXT,
|
|
174
|
-
is_active INTEGER DEFAULT 1
|
|
175
|
-
);`, (err) => {
|
|
176
|
-
if (err)
|
|
177
|
-
console.error("[MCP] Init access_keys table error:", err.message);
|
|
178
|
-
});
|
|
120
|
+
db.run("CREATE TABLE IF NOT EXISTS cybermem_stats (id INTEGER PRIMARY KEY AUTOINCREMENT, client_name TEXT NOT NULL, operation TEXT NOT NULL, count INTEGER DEFAULT 0, errors INTEGER DEFAULT 0, last_updated INTEGER NOT NULL, UNIQUE(client_name, operation));");
|
|
121
|
+
db.run("CREATE TABLE IF NOT EXISTS cybermem_access_log (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, client_name TEXT NOT NULL, client_version TEXT, method TEXT NOT NULL, endpoint TEXT NOT NULL, operation TEXT NOT NULL, status TEXT NOT NULL, is_error INTEGER DEFAULT 0);");
|
|
122
|
+
db.run("CREATE TABLE IF NOT EXISTS access_keys (id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))), key_hash TEXT NOT NULL, name TEXT DEFAULT 'default', user_id TEXT DEFAULT 'default', created_at TEXT DEFAULT (datetime('now')), last_used_at TEXT, is_active INTEGER DEFAULT 1);");
|
|
179
123
|
});
|
|
180
124
|
db.close();
|
|
181
125
|
}
|
|
@@ -184,302 +128,188 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
184
128
|
server._memoryReady = false;
|
|
185
129
|
}
|
|
186
130
|
}
|
|
187
|
-
//
|
|
131
|
+
// PERSISTENT LOGGING DB
|
|
132
|
+
let loggingDb = null;
|
|
133
|
+
const initLoggingDb = async () => {
|
|
134
|
+
if (loggingDb || cliUrl)
|
|
135
|
+
return loggingDb;
|
|
136
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
137
|
+
const sqlite3 = await import("sqlite3");
|
|
138
|
+
loggingDb = new sqlite3.default.Database(dbPath);
|
|
139
|
+
loggingDb.configure("busyTimeout", 10000);
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
loggingDb.serialize(() => {
|
|
142
|
+
loggingDb.run("PRAGMA journal_mode=WAL;");
|
|
143
|
+
loggingDb.run("PRAGMA synchronous=NORMAL;", () => resolve(loggingDb));
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
};
|
|
188
147
|
const logActivity = async (operation, opts = {}) => {
|
|
189
148
|
if (cliUrl || !memory)
|
|
190
149
|
return;
|
|
191
150
|
const { client: providedClient, method = "POST", endpoint = "/mcp", status = 200, } = opts;
|
|
192
151
|
const ctx = requestContext.getStore();
|
|
193
|
-
const client = providedClient ||
|
|
152
|
+
const client = providedClient ||
|
|
153
|
+
ctx?.clientName ||
|
|
154
|
+
stdioClientName ||
|
|
155
|
+
"antigravity-client";
|
|
194
156
|
try {
|
|
195
|
-
const
|
|
196
|
-
const sqlite3 = await import("sqlite3");
|
|
197
|
-
const db = new sqlite3.default.Database(dbPath);
|
|
198
|
-
db.configure("busyTimeout", 5000);
|
|
157
|
+
const db = (await initLoggingDb());
|
|
199
158
|
const ts = Date.now();
|
|
200
159
|
const is_error = status >= 400 ? 1 : 0;
|
|
201
|
-
console.error(`[MCP] Logging ${operation} for ${client} (status: ${status})`);
|
|
202
160
|
db.serialize(() => {
|
|
203
|
-
|
|
204
|
-
db.run(`INSERT INTO cybermem_access_log
|
|
205
|
-
(timestamp, client_name, client_version, method, endpoint, operation, status, is_error)
|
|
206
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
161
|
+
db.run("INSERT INTO cybermem_access_log (timestamp, client_name, client_version, method, endpoint, operation, status, is_error) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
207
162
|
ts,
|
|
208
163
|
client,
|
|
209
|
-
"0.
|
|
164
|
+
"0.12.4",
|
|
210
165
|
method,
|
|
211
166
|
endpoint,
|
|
212
167
|
operation,
|
|
213
168
|
status.toString(),
|
|
214
169
|
is_error,
|
|
215
|
-
]
|
|
216
|
-
|
|
217
|
-
console.error("[MCP] Log access error:", err.message);
|
|
218
|
-
});
|
|
219
|
-
// Log to stats (Upsert)
|
|
220
|
-
db.run(`INSERT INTO cybermem_stats (client_name, operation, count, errors, last_updated)
|
|
221
|
-
VALUES (?, ?, 1, ?, ?)
|
|
222
|
-
ON CONFLICT(client_name, operation) DO UPDATE SET
|
|
223
|
-
count = count + 1,
|
|
224
|
-
errors = errors + ?,
|
|
225
|
-
last_updated = ?`, [client, operation, is_error, ts, is_error, ts], (err) => {
|
|
226
|
-
if (err)
|
|
227
|
-
console.error("[MCP] Log stats error:", err.message);
|
|
228
|
-
});
|
|
170
|
+
]);
|
|
171
|
+
db.run("INSERT INTO cybermem_stats (client_name, operation, count, errors, last_updated) VALUES (?, ?, 1, ?, ?) ON CONFLICT(client_name, operation) DO UPDATE SET count = count + 1, errors = errors + ?, last_updated = ?", [client, operation, is_error, ts, is_error, ts]);
|
|
229
172
|
});
|
|
230
|
-
db.close();
|
|
231
173
|
}
|
|
232
|
-
catch
|
|
233
|
-
console.error("Failed to log activity to SQLite:", e);
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
const addSourceTag = (tags = []) => {
|
|
237
|
-
if (!tags.some((t) => t.startsWith("source:"))) {
|
|
238
|
-
const clientName = requestContext.getStore()?.clientName || stdioClientName || "unknown";
|
|
239
|
-
tags.push(`source:${clientName}`);
|
|
240
|
-
}
|
|
241
|
-
return tags;
|
|
242
|
-
};
|
|
243
|
-
// Helper to get current User ID from context or args
|
|
244
|
-
const getContextUserId = (argsUserId) => {
|
|
245
|
-
const store = requestContext.getStore();
|
|
246
|
-
return argsUserId || store?.userId;
|
|
174
|
+
catch { }
|
|
247
175
|
};
|
|
248
|
-
//
|
|
176
|
+
// TOOLS
|
|
249
177
|
server.registerTool("add_memory", {
|
|
250
|
-
description: "Store a new memory. " +
|
|
178
|
+
description: "Store a new memory. Use for high-quality, stable data. " +
|
|
179
|
+
CYBERMEM_INSTRUCTIONS,
|
|
251
180
|
inputSchema: zod_1.z.object({
|
|
252
181
|
content: zod_1.z.string(),
|
|
253
|
-
user_id: zod_1.z.string().optional(),
|
|
254
182
|
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
255
183
|
}),
|
|
256
184
|
}, async (args) => {
|
|
257
|
-
const tags = addSourceTag(args.tags);
|
|
258
|
-
const userId = getContextUserId(args.user_id);
|
|
259
185
|
if (cliUrl) {
|
|
260
|
-
const res = await apiClient.post("/add",
|
|
261
|
-
...args,
|
|
262
|
-
user_id: userId,
|
|
263
|
-
tags,
|
|
264
|
-
});
|
|
186
|
+
const res = await apiClient.post("/add", args);
|
|
265
187
|
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
266
188
|
}
|
|
267
189
|
else {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
endpoint: "/memory/add",
|
|
276
|
-
status: 200,
|
|
277
|
-
});
|
|
278
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
279
|
-
}
|
|
280
|
-
catch (e) {
|
|
281
|
-
await logActivity("create", {
|
|
282
|
-
method: "POST",
|
|
283
|
-
endpoint: "/memory/add",
|
|
284
|
-
status: 500,
|
|
285
|
-
});
|
|
286
|
-
throw e;
|
|
287
|
-
}
|
|
190
|
+
const res = await memory.add(args.content, { tags: args.tags });
|
|
191
|
+
await logActivity("create", {
|
|
192
|
+
method: "POST",
|
|
193
|
+
endpoint: "/memory/add",
|
|
194
|
+
status: 200,
|
|
195
|
+
});
|
|
196
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
288
197
|
}
|
|
289
198
|
});
|
|
290
199
|
server.registerTool("query_memory", {
|
|
291
200
|
description: "Search memories.",
|
|
292
201
|
inputSchema: zod_1.z.object({ query: zod_1.z.string(), k: zod_1.z.number().default(5) }),
|
|
293
202
|
}, async (args) => {
|
|
294
|
-
const userId = getContextUserId(); // Search is scoped to user if provided
|
|
295
203
|
if (cliUrl) {
|
|
296
204
|
const res = await apiClient.post("/query", args);
|
|
297
205
|
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
298
206
|
}
|
|
299
207
|
else {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
endpoint: "/memory/query",
|
|
308
|
-
status: 200,
|
|
309
|
-
});
|
|
310
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
311
|
-
}
|
|
312
|
-
catch (e) {
|
|
313
|
-
await logActivity("read", {
|
|
314
|
-
method: "POST",
|
|
315
|
-
endpoint: "/memory/query",
|
|
316
|
-
status: 500,
|
|
317
|
-
});
|
|
318
|
-
throw e;
|
|
319
|
-
}
|
|
208
|
+
const res = await memory.search(args.query, { limit: args.k });
|
|
209
|
+
await logActivity("read", {
|
|
210
|
+
method: "POST",
|
|
211
|
+
endpoint: "/memory/query",
|
|
212
|
+
status: 200,
|
|
213
|
+
});
|
|
214
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
320
215
|
}
|
|
321
216
|
});
|
|
322
|
-
server.registerTool("
|
|
323
|
-
description: "
|
|
324
|
-
inputSchema: zod_1.z.object({
|
|
217
|
+
server.registerTool("update_memory", {
|
|
218
|
+
description: "Mutate existing memory (content/tags). HIGH COST: re-embeds and re-links. Use for corrections.",
|
|
219
|
+
inputSchema: zod_1.z.object({
|
|
220
|
+
id: zod_1.z.string(),
|
|
221
|
+
content: zod_1.z.string().optional(),
|
|
222
|
+
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
223
|
+
}),
|
|
325
224
|
}, async (args) => {
|
|
326
|
-
const userId = getContextUserId();
|
|
327
225
|
if (cliUrl) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
content: [{ type: "text", text: JSON.stringify(res.data) }],
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
const res = await apiClient.post("/query", {
|
|
336
|
-
query: "",
|
|
337
|
-
k: args.limit,
|
|
338
|
-
});
|
|
339
|
-
return {
|
|
340
|
-
content: [{ type: "text", text: JSON.stringify(res.data) }],
|
|
341
|
-
};
|
|
342
|
-
}
|
|
226
|
+
const res = await apiClient.patch(`/memory/${args.id}`, args);
|
|
227
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
343
228
|
}
|
|
344
229
|
else {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
endpoint: "/memory/all",
|
|
230
|
+
if (!sdk_update_memory)
|
|
231
|
+
throw new Error("Update not available in SDK");
|
|
232
|
+
const res = await sdk_update_memory(args.id, args.content, args.tags);
|
|
233
|
+
await logActivity("update", {
|
|
234
|
+
method: "PATCH",
|
|
235
|
+
endpoint: `/memory/${args.id}`,
|
|
352
236
|
status: 200,
|
|
353
237
|
});
|
|
354
238
|
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
355
239
|
}
|
|
356
240
|
});
|
|
241
|
+
server.registerTool("reinforce_memory", {
|
|
242
|
+
description: "Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
|
|
243
|
+
inputSchema: zod_1.z.object({ id: zod_1.z.string(), boost: zod_1.z.number().default(0.1) }),
|
|
244
|
+
}, async (args) => {
|
|
245
|
+
if (cliUrl) {
|
|
246
|
+
const res = await apiClient.post(`/memory/${args.id}/reinforce`, args);
|
|
247
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
if (!sdk_reinforce_memory)
|
|
251
|
+
throw new Error("Reinforce not available in SDK");
|
|
252
|
+
await sdk_reinforce_memory(args.id, args.boost);
|
|
253
|
+
await logActivity("update", {
|
|
254
|
+
method: "POST",
|
|
255
|
+
endpoint: `/memory/${args.id}/reinforce`,
|
|
256
|
+
status: 200,
|
|
257
|
+
});
|
|
258
|
+
return { content: [{ type: "text", text: "Reinforced" }] };
|
|
259
|
+
}
|
|
260
|
+
});
|
|
357
261
|
server.registerTool("delete_memory", {
|
|
358
|
-
description: "Delete memory
|
|
262
|
+
description: "Delete memory",
|
|
359
263
|
inputSchema: zod_1.z.object({ id: zod_1.z.string() }),
|
|
360
264
|
}, async (args) => {
|
|
361
265
|
if (cliUrl) {
|
|
362
|
-
const res = await apiClient.delete(
|
|
266
|
+
const res = await apiClient.delete(`/memory/${args.id}`);
|
|
363
267
|
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
364
268
|
}
|
|
365
269
|
else {
|
|
366
270
|
const dbPath = process.env.OM_DB_PATH;
|
|
367
271
|
const sqlite3 = await import("sqlite3");
|
|
368
272
|
const db = new sqlite3.default.Database(dbPath);
|
|
369
|
-
db.configure("busyTimeout", 5000);
|
|
370
273
|
return new Promise((resolve, reject) => {
|
|
371
274
|
db.serialize(() => {
|
|
372
|
-
db.run("BEGIN TRANSACTION");
|
|
373
275
|
db.run("DELETE FROM memories WHERE id = ?", [args.id]);
|
|
374
|
-
db.run("DELETE FROM vectors WHERE id = ?", [args.id])
|
|
375
|
-
db.run("DELETE FROM waypoints WHERE src_id = ? OR dst_id = ?", [
|
|
376
|
-
args.id,
|
|
377
|
-
args.id,
|
|
378
|
-
]);
|
|
379
|
-
db.run("COMMIT", async (err) => {
|
|
276
|
+
db.run("DELETE FROM vectors WHERE id = ?", [args.id], async (err) => {
|
|
380
277
|
db.close();
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
reject(
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
await logActivity("delete", {
|
|
391
|
-
method: "DELETE",
|
|
392
|
-
endpoint: `/memory/${args.id}`,
|
|
393
|
-
status: 200,
|
|
394
|
-
});
|
|
395
|
-
resolve({
|
|
396
|
-
content: [
|
|
397
|
-
{ type: "text", text: `Memory ${args.id} deleted` },
|
|
398
|
-
],
|
|
399
|
-
});
|
|
400
|
-
}
|
|
278
|
+
await logActivity("delete", {
|
|
279
|
+
method: "DELETE",
|
|
280
|
+
endpoint: `/memory/${args.id}`,
|
|
281
|
+
status: err ? 500 : 200,
|
|
282
|
+
});
|
|
283
|
+
if (err)
|
|
284
|
+
reject(err);
|
|
285
|
+
else
|
|
286
|
+
resolve({ content: [{ type: "text", text: "Deleted" }] });
|
|
401
287
|
});
|
|
402
288
|
});
|
|
403
289
|
});
|
|
404
290
|
}
|
|
405
291
|
});
|
|
406
|
-
|
|
407
|
-
description: "Update memory",
|
|
408
|
-
inputSchema: zod_1.z.object({ id: zod_1.z.string(), content: zod_1.z.string().optional() }),
|
|
409
|
-
}, async (args) => {
|
|
410
|
-
await logActivity("update", {
|
|
411
|
-
method: "PATCH",
|
|
412
|
-
endpoint: `/memory/${args.id}`,
|
|
413
|
-
status: 501,
|
|
414
|
-
});
|
|
415
|
-
return { content: [{ type: "text", text: "Update not implemented" }] };
|
|
416
|
-
});
|
|
417
|
-
// --- TRANSPORT ---
|
|
292
|
+
// EXPRESS SERVER
|
|
418
293
|
const useHttp = args.includes("--http") || args.includes("--port");
|
|
419
294
|
if (useHttp) {
|
|
420
295
|
const port = parseInt(getArg("--port") || "3100", 10);
|
|
421
296
|
const app = (0, express_1.default)();
|
|
422
297
|
app.use((0, cors_1.default)());
|
|
423
298
|
app.use(express_1.default.json());
|
|
424
|
-
app.get("/health", (req, res) => res.json({
|
|
425
|
-
ok: server._memoryReady,
|
|
426
|
-
version: "0.7.5",
|
|
427
|
-
mode: cliUrl ? "proxy" : "sdk",
|
|
428
|
-
ready: server._memoryReady,
|
|
429
|
-
}));
|
|
430
|
-
app.get("/metrics", async (req, res) => {
|
|
431
|
-
try {
|
|
432
|
-
const dbPath = process.env.OM_DB_PATH;
|
|
433
|
-
const sqlite3 = await import("sqlite3");
|
|
434
|
-
const db = new sqlite3.default.Database(dbPath);
|
|
435
|
-
db.configure("busyTimeout", 5000);
|
|
436
|
-
const getCount = (query) => new Promise((resolve) => db.get(query, (err, row) => resolve(row?.count || 0)));
|
|
437
|
-
const memoriesCount = await getCount("SELECT COUNT(*) as count FROM memories");
|
|
438
|
-
const totalRequests = await getCount("SELECT COUNT(*) as count FROM cybermem_access_log");
|
|
439
|
-
const errorRequests = await getCount("SELECT COUNT(*) as count FROM cybermem_access_log WHERE is_error = 1");
|
|
440
|
-
const uniqueClients = await getCount("SELECT COUNT(DISTINCT client_name) as count FROM cybermem_access_log");
|
|
441
|
-
db.close();
|
|
442
|
-
const metrics = [
|
|
443
|
-
"# HELP openmemory_memories_total Total number of memories",
|
|
444
|
-
"# TYPE openmemory_memories_total gauge",
|
|
445
|
-
`openmemory_memories_total ${memoriesCount}`,
|
|
446
|
-
"# HELP openmemory_requests_aggregate_total Total requests logged in SQLite",
|
|
447
|
-
"# TYPE openmemory_requests_aggregate_total counter",
|
|
448
|
-
`openmemory_requests_aggregate_total ${totalRequests}`,
|
|
449
|
-
"# HELP openmemory_errors_total Total errors logged in SQLite",
|
|
450
|
-
"# TYPE openmemory_errors_total counter",
|
|
451
|
-
`openmemory_errors_total ${errorRequests}`,
|
|
452
|
-
"# HELP openmemory_clients_total Total unique clients logged in SQLite",
|
|
453
|
-
"# TYPE openmemory_clients_total gauge",
|
|
454
|
-
`openmemory_clients_total ${uniqueClients}`,
|
|
455
|
-
"# HELP openmemory_success_rate_aggregate Success rate from SQLite logs",
|
|
456
|
-
"# TYPE openmemory_success_rate_aggregate gauge",
|
|
457
|
-
`openmemory_success_rate_aggregate ${totalRequests > 0 ? ((totalRequests - errorRequests) / totalRequests) * 100 : 100}`,
|
|
458
|
-
].join("\n");
|
|
459
|
-
res.set("Content-Type", "text/plain").send(metrics);
|
|
460
|
-
}
|
|
461
|
-
catch (e) {
|
|
462
|
-
res.status(500).send(`# Error: ${e.message}`);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
299
|
+
app.get("/health", (req, res) => res.json({ ok: true, version: "0.12.4" }));
|
|
465
300
|
app.use((req, res, next) => {
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
requestContext.run({ userId, clientName }, next);
|
|
301
|
+
const clientName = req.headers["x-client-name"] || "antigravity-client";
|
|
302
|
+
requestContext.run({ clientName }, next);
|
|
303
|
+
// next(); // DELETED! Correctly handled by requestContext.run
|
|
470
304
|
});
|
|
471
305
|
if (!cliUrl && memory) {
|
|
472
306
|
app.post("/add", async (req, res) => {
|
|
473
307
|
try {
|
|
474
|
-
const
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
const result = await memory.add(content, {
|
|
478
|
-
user_id: user_id || contextUserId,
|
|
479
|
-
tags: finalTags,
|
|
308
|
+
const result = await memory.add(req.body.content, {
|
|
309
|
+
id: req.body.id,
|
|
310
|
+
tags: req.body.tags,
|
|
480
311
|
});
|
|
481
312
|
await logActivity("create", {
|
|
482
|
-
client: "rest-api",
|
|
483
313
|
method: "POST",
|
|
484
314
|
endpoint: "/add",
|
|
485
315
|
status: 200,
|
|
@@ -487,25 +317,15 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
487
317
|
res.json(result);
|
|
488
318
|
}
|
|
489
319
|
catch (e) {
|
|
490
|
-
await logActivity("create", {
|
|
491
|
-
client: "rest-api",
|
|
492
|
-
method: "POST",
|
|
493
|
-
endpoint: "/add",
|
|
494
|
-
status: 500,
|
|
495
|
-
});
|
|
496
320
|
res.status(500).json({ error: e.message });
|
|
497
321
|
}
|
|
498
322
|
});
|
|
499
323
|
app.post("/query", async (req, res) => {
|
|
500
324
|
try {
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
const result = await memory.search(query || "", {
|
|
504
|
-
limit: k || 5,
|
|
505
|
-
user_id: contextUserId,
|
|
325
|
+
const result = await memory.search(req.body.query || "", {
|
|
326
|
+
limit: req.body.k || 5,
|
|
506
327
|
});
|
|
507
328
|
await logActivity("read", {
|
|
508
|
-
client: "rest-api",
|
|
509
329
|
method: "POST",
|
|
510
330
|
endpoint: "/query",
|
|
511
331
|
status: 200,
|
|
@@ -513,25 +333,13 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
513
333
|
res.json(result);
|
|
514
334
|
}
|
|
515
335
|
catch (e) {
|
|
516
|
-
await logActivity("read", {
|
|
517
|
-
client: "rest-api",
|
|
518
|
-
method: "POST",
|
|
519
|
-
endpoint: "/query",
|
|
520
|
-
status: 500,
|
|
521
|
-
});
|
|
522
336
|
res.status(500).json({ error: e.message });
|
|
523
337
|
}
|
|
524
338
|
});
|
|
525
339
|
app.get("/all", async (req, res) => {
|
|
526
340
|
try {
|
|
527
|
-
const
|
|
528
|
-
const limit = parseInt(req.query.limit) || 10;
|
|
529
|
-
const result = await memory.search("", {
|
|
530
|
-
limit,
|
|
531
|
-
user_id: contextUserId,
|
|
532
|
-
});
|
|
341
|
+
const result = await memory.search("", { limit: 10 });
|
|
533
342
|
await logActivity("read", {
|
|
534
|
-
client: "rest-api",
|
|
535
343
|
method: "GET",
|
|
536
344
|
endpoint: "/all",
|
|
537
345
|
status: 200,
|
|
@@ -539,15 +347,51 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
539
347
|
res.json(result);
|
|
540
348
|
}
|
|
541
349
|
catch (e) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
350
|
+
res.status(500).json({ error: e.message });
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
app.patch("/memory/:id", async (req, res) => {
|
|
354
|
+
try {
|
|
355
|
+
const result = await sdk_update_memory(req.params.id, req.body.content, req.body.tags, req.body.metadata);
|
|
356
|
+
await logActivity("update", {
|
|
357
|
+
method: "PATCH",
|
|
358
|
+
endpoint: `/memory/${req.params.id}`,
|
|
359
|
+
status: 200,
|
|
547
360
|
});
|
|
361
|
+
res.json(result);
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
548
364
|
res.status(500).json({ error: e.message });
|
|
549
365
|
}
|
|
550
366
|
});
|
|
367
|
+
app.post("/memory/:id/reinforce", async (req, res) => {
|
|
368
|
+
try {
|
|
369
|
+
await sdk_reinforce_memory(req.params.id, req.body.boost);
|
|
370
|
+
await logActivity("update", {
|
|
371
|
+
method: "POST",
|
|
372
|
+
endpoint: `/memory/${req.params.id}/reinforce`,
|
|
373
|
+
status: 200,
|
|
374
|
+
});
|
|
375
|
+
res.json({ ok: true });
|
|
376
|
+
}
|
|
377
|
+
catch (e) {
|
|
378
|
+
res.status(500).json({ error: e.message });
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
app.delete("/memory/:id", async (req, res) => {
|
|
382
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
383
|
+
const sqlite3 = await import("sqlite3");
|
|
384
|
+
const db = new sqlite3.default.Database(dbPath);
|
|
385
|
+
db.run("DELETE FROM memories WHERE id = ?", [req.params.id], async () => {
|
|
386
|
+
db.close();
|
|
387
|
+
await logActivity("delete", {
|
|
388
|
+
method: "DELETE",
|
|
389
|
+
endpoint: `/memory/${req.params.id}`,
|
|
390
|
+
status: 200,
|
|
391
|
+
});
|
|
392
|
+
res.json({ ok: true });
|
|
393
|
+
});
|
|
394
|
+
});
|
|
551
395
|
}
|
|
552
396
|
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
|
|
553
397
|
sessionIdGenerator: () => crypto.randomUUID(),
|
|
@@ -555,9 +399,7 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
555
399
|
app.all("/mcp", async (req, res) => await transport.handleRequest(req, res, req.body));
|
|
556
400
|
app.all("/sse", async (req, res) => await transport.handleRequest(req, res, req.body));
|
|
557
401
|
server.connect(transport).then(() => {
|
|
558
|
-
app.listen(port, () => {
|
|
559
|
-
console.error(`CyberMem MCP (ready: ${server._memoryReady}) running on http://localhost:${port}`);
|
|
560
|
-
});
|
|
402
|
+
app.listen(port, () => console.error(`CyberMem MCP running on http://localhost:${port}`));
|
|
561
403
|
});
|
|
562
404
|
}
|
|
563
405
|
else {
|