@cybermem/mcp 0.8.3 → 0.8.7
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/dist/env.js +14 -0
- package/dist/index.js +377 -67
- package/package.json +5 -2
- package/src/env.ts +10 -0
- package/src/index.ts +474 -78
- package/src/auth.ts +0 -244
package/dist/env.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
/**
|
|
7
|
+
* Environment Initialization for CyberMem MCP
|
|
8
|
+
* Must be imported first to set side-effect vars.
|
|
9
|
+
*/
|
|
10
|
+
const dotenv_1 = __importDefault(require("dotenv"));
|
|
11
|
+
dotenv_1.default.config();
|
|
12
|
+
process.env.OM_TIER = process.env.OM_TIER || "hybrid";
|
|
13
|
+
process.env.OM_PORT = process.env.OM_PORT || "0";
|
|
14
|
+
process.env.PORT = process.env.PORT || "0";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
require("./env.js");
|
|
3
8
|
/**
|
|
4
9
|
* CyberMem MCP Server
|
|
5
10
|
*
|
|
@@ -7,50 +12,42 @@
|
|
|
7
12
|
* 1. Local/Server Mode (default): Uses openmemory-js SDK directly.
|
|
8
13
|
* 2. Remote Client Mode (with --url): Proxies requests to a remote CyberMem server via HTTP.
|
|
9
14
|
*/
|
|
10
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
-
};
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
15
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
15
16
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
16
17
|
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
18
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
19
|
+
const async_hooks_1 = require("async_hooks");
|
|
17
20
|
const axios_1 = __importDefault(require("axios"));
|
|
18
21
|
const cors_1 = __importDefault(require("cors"));
|
|
19
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
20
22
|
const express_1 = __importDefault(require("express"));
|
|
21
|
-
const openmemory_js_1 = require("openmemory-js");
|
|
22
23
|
const zod_1 = require("zod");
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
+
// Async Storage for Request Context (User ID and Client Name)
|
|
38
|
+
const requestContext = new async_hooks_1.AsyncLocalStorage();
|
|
39
|
+
// CLI args processing
|
|
26
40
|
const args = process.argv.slice(2);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
.then(() => process.exit(0))
|
|
30
|
-
.catch((err) => {
|
|
31
|
-
console.error("Login failed:", err.message);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
else if (args.includes("--logout")) {
|
|
36
|
-
(0, auth_js_1.logout)();
|
|
37
|
-
process.exit(0);
|
|
38
|
-
}
|
|
39
|
-
else if (args.includes("--status")) {
|
|
40
|
-
(0, auth_js_1.showStatus)();
|
|
41
|
-
process.exit(0);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
startServer();
|
|
45
|
-
}
|
|
41
|
+
// Start the server
|
|
42
|
+
startServer();
|
|
46
43
|
async function startServer() {
|
|
47
44
|
const getArg = (name) => {
|
|
48
45
|
const idx = args.indexOf(name);
|
|
49
46
|
return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
|
|
50
47
|
};
|
|
51
|
-
const cliClientName = getArg("--client-name") || "cybermem-mcp";
|
|
52
48
|
const cliUrl = getArg("--url");
|
|
53
|
-
const
|
|
49
|
+
const cliToken = getArg("--token") || getArg("--api-key");
|
|
50
|
+
let stdioClientName = undefined;
|
|
54
51
|
// Protocol Instructions
|
|
55
52
|
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
56
53
|
PROTOCOL:
|
|
@@ -58,7 +55,7 @@ PROTOCOL:
|
|
|
58
55
|
2. Store new insights immediately with add_memory (FULL content)
|
|
59
56
|
3. Always include tags: [topic, year, source:your-client-name]
|
|
60
57
|
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
61
|
-
const server = new mcp_js_1.McpServer({ name: "cybermem", version: "0.
|
|
58
|
+
const server = new mcp_js_1.McpServer({ name: "cybermem", version: "0.7.5" }, {
|
|
62
59
|
instructions: CYBERMEM_INSTRUCTIONS,
|
|
63
60
|
});
|
|
64
61
|
server.registerResource("CyberMem Agent Protocol", "cybermem://protocol", { description: "Instructions for AI agents", mimeType: "text/plain" }, async () => ({
|
|
@@ -70,6 +67,23 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
70
67
|
},
|
|
71
68
|
],
|
|
72
69
|
}));
|
|
70
|
+
// Capture client info from handshake
|
|
71
|
+
// @ts-ignore - access underlying server
|
|
72
|
+
server.server.setRequestHandler(types_js_1.InitializeRequestSchema, async (request) => {
|
|
73
|
+
stdioClientName = request.params.clientInfo.name;
|
|
74
|
+
console.error(`[MCP] Client identified via handshake: ${stdioClientName}`);
|
|
75
|
+
return {
|
|
76
|
+
protocolVersion: "2024-11-05",
|
|
77
|
+
capabilities: {
|
|
78
|
+
tools: { listChanged: true },
|
|
79
|
+
resources: { subscribe: true },
|
|
80
|
+
},
|
|
81
|
+
serverInfo: {
|
|
82
|
+
name: "cybermem",
|
|
83
|
+
version: "0.7.5",
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
});
|
|
73
87
|
// --- IMPLEMENTATION LOGIC ---
|
|
74
88
|
let memory = null;
|
|
75
89
|
let apiClient = null;
|
|
@@ -79,35 +93,158 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
79
93
|
apiClient = axios_1.default.create({
|
|
80
94
|
baseURL: cliUrl,
|
|
81
95
|
headers: {
|
|
82
|
-
|
|
83
|
-
"
|
|
96
|
+
"X-API-Key": cliToken,
|
|
97
|
+
Accept: "application/json, text/event-stream",
|
|
98
|
+
"Content-Type": "application/json",
|
|
84
99
|
},
|
|
85
100
|
});
|
|
101
|
+
// Dynamically inject client name from context or discovery
|
|
102
|
+
apiClient.interceptors.request.use((config) => {
|
|
103
|
+
const ctx = requestContext.getStore();
|
|
104
|
+
config.headers["X-Client-Name"] =
|
|
105
|
+
ctx?.clientName || stdioClientName || "unknown-mcp-client";
|
|
106
|
+
return config;
|
|
107
|
+
});
|
|
86
108
|
}
|
|
87
109
|
else {
|
|
88
110
|
// LOCAL SDK MODE
|
|
89
111
|
const homedir = process.env.HOME || process.env.USERPROFILE || "";
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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;
|
|
94
116
|
// Ensure directory exists
|
|
95
|
-
const fs =
|
|
117
|
+
const fs = await import("fs");
|
|
96
118
|
try {
|
|
97
|
-
const
|
|
98
|
-
const dir = dbPath.substring(0, dbPath.lastIndexOf("/"));
|
|
119
|
+
const dir = path.dirname(dbPath);
|
|
99
120
|
if (dir)
|
|
100
121
|
fs.mkdirSync(dir, { recursive: true });
|
|
101
122
|
}
|
|
102
123
|
catch { }
|
|
103
|
-
|
|
124
|
+
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
|
+
const { Memory } = await import("openmemory-js/dist/core/memory.js");
|
|
129
|
+
memory = new Memory();
|
|
130
|
+
server._memoryReady = true;
|
|
131
|
+
// --- INITIALIZE LOGGING TABLES ---
|
|
132
|
+
const sqlite3 = await import("sqlite3");
|
|
133
|
+
const db = new sqlite3.default.Database(dbPath);
|
|
134
|
+
db.configure("busyTimeout", 5000);
|
|
135
|
+
db.serialize(() => {
|
|
136
|
+
db.run("PRAGMA journal_mode=WAL;", (err) => {
|
|
137
|
+
if (err)
|
|
138
|
+
console.error("[MCP] Init WAL error:", err.message);
|
|
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
|
+
});
|
|
179
|
+
});
|
|
180
|
+
db.close();
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
console.error("Failed to initialize OpenMemory SDK:", e);
|
|
184
|
+
server._memoryReady = false;
|
|
185
|
+
}
|
|
104
186
|
}
|
|
105
|
-
// Helper to
|
|
187
|
+
// Helper to log activity to SQLite (Local SDK Mode only)
|
|
188
|
+
const logActivity = async (operation, opts = {}) => {
|
|
189
|
+
if (cliUrl || !memory)
|
|
190
|
+
return;
|
|
191
|
+
const { client: providedClient, method = "POST", endpoint = "/mcp", status = 200, } = opts;
|
|
192
|
+
const ctx = requestContext.getStore();
|
|
193
|
+
const client = providedClient || ctx?.clientName || stdioClientName || "unknown-client";
|
|
194
|
+
try {
|
|
195
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
196
|
+
const sqlite3 = await import("sqlite3");
|
|
197
|
+
const db = new sqlite3.default.Database(dbPath);
|
|
198
|
+
db.configure("busyTimeout", 5000);
|
|
199
|
+
const ts = Date.now();
|
|
200
|
+
const is_error = status >= 400 ? 1 : 0;
|
|
201
|
+
console.error(`[MCP] Logging ${operation} for ${client} (status: ${status})`);
|
|
202
|
+
db.serialize(() => {
|
|
203
|
+
// Log to access_log
|
|
204
|
+
db.run(`INSERT INTO cybermem_access_log
|
|
205
|
+
(timestamp, client_name, client_version, method, endpoint, operation, status, is_error)
|
|
206
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
|
|
207
|
+
ts,
|
|
208
|
+
client,
|
|
209
|
+
"0.7.0",
|
|
210
|
+
method,
|
|
211
|
+
endpoint,
|
|
212
|
+
operation,
|
|
213
|
+
status.toString(),
|
|
214
|
+
is_error,
|
|
215
|
+
], (err) => {
|
|
216
|
+
if (err)
|
|
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
|
+
});
|
|
229
|
+
});
|
|
230
|
+
db.close();
|
|
231
|
+
}
|
|
232
|
+
catch (e) {
|
|
233
|
+
console.error("Failed to log activity to SQLite:", e);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
106
236
|
const addSourceTag = (tags = []) => {
|
|
107
|
-
if (!tags.some((t) => t.startsWith("source:")))
|
|
108
|
-
|
|
237
|
+
if (!tags.some((t) => t.startsWith("source:"))) {
|
|
238
|
+
const clientName = requestContext.getStore()?.clientName || stdioClientName || "unknown";
|
|
239
|
+
tags.push(`source:${clientName}`);
|
|
240
|
+
}
|
|
109
241
|
return tags;
|
|
110
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;
|
|
247
|
+
};
|
|
111
248
|
// --- TOOLS ---
|
|
112
249
|
server.registerTool("add_memory", {
|
|
113
250
|
description: "Store a new memory. " + CYBERMEM_INSTRUCTIONS,
|
|
@@ -118,38 +255,76 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
118
255
|
}),
|
|
119
256
|
}, async (args) => {
|
|
120
257
|
const tags = addSourceTag(args.tags);
|
|
258
|
+
const userId = getContextUserId(args.user_id);
|
|
121
259
|
if (cliUrl) {
|
|
122
|
-
const res = await apiClient.post("/add", {
|
|
260
|
+
const res = await apiClient.post("/add", {
|
|
261
|
+
...args,
|
|
262
|
+
user_id: userId,
|
|
263
|
+
tags,
|
|
264
|
+
});
|
|
123
265
|
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
124
266
|
}
|
|
125
267
|
else {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
268
|
+
try {
|
|
269
|
+
const res = await memory.add(args.content, {
|
|
270
|
+
user_id: userId,
|
|
271
|
+
tags,
|
|
272
|
+
});
|
|
273
|
+
await logActivity("create", {
|
|
274
|
+
method: "POST",
|
|
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
|
+
}
|
|
131
288
|
}
|
|
132
289
|
});
|
|
133
290
|
server.registerTool("query_memory", {
|
|
134
291
|
description: "Search memories.",
|
|
135
292
|
inputSchema: zod_1.z.object({ query: zod_1.z.string(), k: zod_1.z.number().default(5) }),
|
|
136
293
|
}, async (args) => {
|
|
294
|
+
const userId = getContextUserId(); // Search is scoped to user if provided
|
|
137
295
|
if (cliUrl) {
|
|
138
296
|
const res = await apiClient.post("/query", args);
|
|
139
297
|
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
140
298
|
}
|
|
141
299
|
else {
|
|
142
|
-
|
|
143
|
-
|
|
300
|
+
try {
|
|
301
|
+
const res = await memory.search(args.query, {
|
|
302
|
+
limit: args.k,
|
|
303
|
+
user_id: userId,
|
|
304
|
+
});
|
|
305
|
+
await logActivity("read", {
|
|
306
|
+
method: "POST",
|
|
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
|
+
}
|
|
144
320
|
}
|
|
145
321
|
});
|
|
146
322
|
server.registerTool("list_memories", {
|
|
147
323
|
description: "List recent memories",
|
|
148
324
|
inputSchema: zod_1.z.object({ limit: zod_1.z.number().default(10) }),
|
|
149
325
|
}, async (args) => {
|
|
326
|
+
const userId = getContextUserId();
|
|
150
327
|
if (cliUrl) {
|
|
151
|
-
// Fallback to /query with empty string if /list not available, or use /all
|
|
152
|
-
// Old API had /all
|
|
153
328
|
try {
|
|
154
329
|
const res = await apiClient.get(`/all?limit=${args.limit}`);
|
|
155
330
|
return {
|
|
@@ -167,7 +342,15 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
167
342
|
}
|
|
168
343
|
}
|
|
169
344
|
else {
|
|
170
|
-
const res = await memory.search("", {
|
|
345
|
+
const res = await memory.search("", {
|
|
346
|
+
limit: args.limit,
|
|
347
|
+
user_id: userId,
|
|
348
|
+
});
|
|
349
|
+
await logActivity("read", {
|
|
350
|
+
method: "GET",
|
|
351
|
+
endpoint: "/memory/all",
|
|
352
|
+
status: 200,
|
|
353
|
+
});
|
|
171
354
|
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
172
355
|
}
|
|
173
356
|
});
|
|
@@ -180,17 +363,55 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
180
363
|
return { content: [{ type: "text", text: JSON.stringify(res.data) }] };
|
|
181
364
|
}
|
|
182
365
|
else {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
366
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
367
|
+
const sqlite3 = await import("sqlite3");
|
|
368
|
+
const db = new sqlite3.default.Database(dbPath);
|
|
369
|
+
db.configure("busyTimeout", 5000);
|
|
370
|
+
return new Promise((resolve, reject) => {
|
|
371
|
+
db.serialize(() => {
|
|
372
|
+
db.run("BEGIN TRANSACTION");
|
|
373
|
+
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) => {
|
|
380
|
+
db.close();
|
|
381
|
+
if (err) {
|
|
382
|
+
await logActivity("delete", {
|
|
383
|
+
method: "DELETE",
|
|
384
|
+
endpoint: `/memory/${args.id}`,
|
|
385
|
+
status: 500,
|
|
386
|
+
});
|
|
387
|
+
reject(new Error(`Failed to delete memory ${args.id}: ${err.message}`));
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
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
|
+
}
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
});
|
|
188
404
|
}
|
|
189
405
|
});
|
|
190
406
|
server.registerTool("update_memory", {
|
|
191
407
|
description: "Update memory",
|
|
192
408
|
inputSchema: zod_1.z.object({ id: zod_1.z.string(), content: zod_1.z.string().optional() }),
|
|
193
409
|
}, async (args) => {
|
|
410
|
+
await logActivity("update", {
|
|
411
|
+
method: "PATCH",
|
|
412
|
+
endpoint: `/memory/${args.id}`,
|
|
413
|
+
status: 501,
|
|
414
|
+
});
|
|
194
415
|
return { content: [{ type: "text", text: "Update not implemented" }] };
|
|
195
416
|
});
|
|
196
417
|
// --- TRANSPORT ---
|
|
@@ -200,41 +421,130 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
200
421
|
const app = (0, express_1.default)();
|
|
201
422
|
app.use((0, cors_1.default)());
|
|
202
423
|
app.use(express_1.default.json());
|
|
203
|
-
app.get("/health", (req, res) => res.json({
|
|
204
|
-
|
|
205
|
-
|
|
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
|
+
});
|
|
465
|
+
app.use((req, res, next) => {
|
|
466
|
+
const userId = req.headers["x-user-id"];
|
|
467
|
+
const clientName = req.headers["x-client-name"] ||
|
|
468
|
+
req.headers["user-agent"];
|
|
469
|
+
requestContext.run({ userId, clientName }, next);
|
|
470
|
+
});
|
|
206
471
|
if (!cliUrl && memory) {
|
|
207
472
|
app.post("/add", async (req, res) => {
|
|
208
473
|
try {
|
|
474
|
+
const contextUserId = requestContext.getStore()?.userId;
|
|
209
475
|
const { content, user_id, tags } = req.body;
|
|
210
476
|
const finalTags = addSourceTag(tags);
|
|
211
477
|
const result = await memory.add(content, {
|
|
212
|
-
user_id,
|
|
478
|
+
user_id: user_id || contextUserId,
|
|
213
479
|
tags: finalTags,
|
|
214
480
|
});
|
|
481
|
+
await logActivity("create", {
|
|
482
|
+
client: "rest-api",
|
|
483
|
+
method: "POST",
|
|
484
|
+
endpoint: "/add",
|
|
485
|
+
status: 200,
|
|
486
|
+
});
|
|
215
487
|
res.json(result);
|
|
216
488
|
}
|
|
217
489
|
catch (e) {
|
|
490
|
+
await logActivity("create", {
|
|
491
|
+
client: "rest-api",
|
|
492
|
+
method: "POST",
|
|
493
|
+
endpoint: "/add",
|
|
494
|
+
status: 500,
|
|
495
|
+
});
|
|
218
496
|
res.status(500).json({ error: e.message });
|
|
219
497
|
}
|
|
220
498
|
});
|
|
221
499
|
app.post("/query", async (req, res) => {
|
|
222
500
|
try {
|
|
501
|
+
const contextUserId = requestContext.getStore()?.userId;
|
|
223
502
|
const { query, k } = req.body;
|
|
224
|
-
const result = await memory.search(query || "", {
|
|
503
|
+
const result = await memory.search(query || "", {
|
|
504
|
+
limit: k || 5,
|
|
505
|
+
user_id: contextUserId,
|
|
506
|
+
});
|
|
507
|
+
await logActivity("read", {
|
|
508
|
+
client: "rest-api",
|
|
509
|
+
method: "POST",
|
|
510
|
+
endpoint: "/query",
|
|
511
|
+
status: 200,
|
|
512
|
+
});
|
|
225
513
|
res.json(result);
|
|
226
514
|
}
|
|
227
515
|
catch (e) {
|
|
516
|
+
await logActivity("read", {
|
|
517
|
+
client: "rest-api",
|
|
518
|
+
method: "POST",
|
|
519
|
+
endpoint: "/query",
|
|
520
|
+
status: 500,
|
|
521
|
+
});
|
|
228
522
|
res.status(500).json({ error: e.message });
|
|
229
523
|
}
|
|
230
524
|
});
|
|
231
525
|
app.get("/all", async (req, res) => {
|
|
232
526
|
try {
|
|
527
|
+
const contextUserId = requestContext.getStore()?.userId;
|
|
233
528
|
const limit = parseInt(req.query.limit) || 10;
|
|
234
|
-
const result = await memory.search("", {
|
|
529
|
+
const result = await memory.search("", {
|
|
530
|
+
limit,
|
|
531
|
+
user_id: contextUserId,
|
|
532
|
+
});
|
|
533
|
+
await logActivity("read", {
|
|
534
|
+
client: "rest-api",
|
|
535
|
+
method: "GET",
|
|
536
|
+
endpoint: "/all",
|
|
537
|
+
status: 200,
|
|
538
|
+
});
|
|
235
539
|
res.json(result);
|
|
236
540
|
}
|
|
237
541
|
catch (e) {
|
|
542
|
+
await logActivity("read", {
|
|
543
|
+
client: "rest-api",
|
|
544
|
+
method: "GET",
|
|
545
|
+
endpoint: "/all",
|
|
546
|
+
status: 500,
|
|
547
|
+
});
|
|
238
548
|
res.status(500).json({ error: e.message });
|
|
239
549
|
}
|
|
240
550
|
});
|
|
@@ -246,7 +556,7 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
246
556
|
app.all("/sse", async (req, res) => await transport.handleRequest(req, res, req.body));
|
|
247
557
|
server.connect(transport).then(() => {
|
|
248
558
|
app.listen(port, () => {
|
|
249
|
-
console.
|
|
559
|
+
console.error(`CyberMem MCP (ready: ${server._memoryReady}) running on http://localhost:${port}`);
|
|
250
560
|
});
|
|
251
561
|
});
|
|
252
562
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cybermem/mcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.7",
|
|
4
4
|
"description": "CyberMem MCP Server - AI Memory with openmemory-js SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"ai-memory",
|
|
26
26
|
"claude",
|
|
27
27
|
"cursor",
|
|
28
|
-
"antigravity",
|
|
29
28
|
"openmemory",
|
|
30
29
|
"llm"
|
|
31
30
|
],
|
|
@@ -40,7 +39,11 @@
|
|
|
40
39
|
"cors": "^2.8.5",
|
|
41
40
|
"dotenv": "^16.0.0",
|
|
42
41
|
"express": "^5.2.1",
|
|
42
|
+
"keytar": "^7.9.0",
|
|
43
|
+
"open": "^11.0.0",
|
|
43
44
|
"openmemory-js": "^1.3.2",
|
|
45
|
+
"sqlite": "^5.1.1",
|
|
46
|
+
"sqlite3": "^5.1.7",
|
|
44
47
|
"zod": "^3.25.76"
|
|
45
48
|
},
|
|
46
49
|
"devDependencies": {
|
package/src/env.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Initialization for CyberMem MCP
|
|
3
|
+
* Must be imported first to set side-effect vars.
|
|
4
|
+
*/
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
process.env.OM_TIER = process.env.OM_TIER || "hybrid";
|
|
9
|
+
process.env.OM_PORT = process.env.OM_PORT || "0";
|
|
10
|
+
process.env.PORT = process.env.PORT || "0";
|