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