@cybermem/mcp 0.14.14 → 0.15.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/src/index.ts CHANGED
@@ -1,597 +1,348 @@
1
+ #!/usr/bin/env node
1
2
  import "./console-fix.js";
2
3
  import "./env.js";
3
4
 
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
6
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { InitializeRequestSchema } from "@modelcontextprotocol/sdk/types.js";
8
5
  import { AsyncLocalStorage } from "async_hooks";
9
- import cors from "cors";
10
- import express from "express";
11
- import { readFileSync } from "fs";
12
- import { join } from "path";
6
+ import { FastMCP } from "fastmcp";
7
+ import { existsSync, mkdirSync, readFileSync } from "fs";
8
+ import { dirname, join } from "path";
13
9
  import { z } from "zod";
14
10
 
15
- // Type definition for OpenMemory Memory class
11
+ // --- CORE SDK IMPORTS ---
12
+ import { all_async, run_async } from "openmemory-js/dist/core/db.js";
13
+ import { Memory } from "openmemory-js/dist/core/memory.js";
14
+ import {
15
+ reinforce_memory,
16
+ update_memory,
17
+ } from "openmemory-js/dist/memory/hsg.js";
18
+
19
+ // --- TYPES ---
16
20
  interface IMemory {
17
- add(
18
- content: string,
19
- opts?: { tags?: string[]; user_id?: string; [key: string]: unknown },
20
- ): Promise<unknown>;
21
- search(
22
- query: string,
23
- opts?: {
24
- limit?: number;
25
- user_id?: string;
26
- sectors?: unknown;
27
- [key: string]: unknown;
28
- },
29
- ): Promise<unknown>;
21
+ add(content: string, opts?: any): Promise<unknown>;
22
+ search(query: string, opts?: any): Promise<unknown>;
30
23
  get(id: string): Promise<unknown>;
31
24
  delete_all(user_id: string): Promise<unknown>;
32
25
  wipe(): Promise<unknown>;
33
26
  }
34
27
 
35
- // Async Storage for Request Context (User ID and Client Name)
36
- const requestContext = new AsyncLocalStorage<{
37
- userId?: string;
38
- clientName?: string;
39
- }>();
40
-
41
- // CLI args processing
42
- const args = process.argv.slice(2);
28
+ // --- GLOBALS & CONTEXT ---
29
+ const requestContext = new AsyncLocalStorage<{ clientName?: string }>();
43
30
 
44
- // Read version from package.json
45
31
  let PACKAGE_VERSION = "0.0.0";
46
32
  try {
47
33
  const packageJsonPath = join(__dirname, "../package.json");
48
34
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
49
35
  PACKAGE_VERSION = packageJson.version;
50
- } catch (error) {
51
- console.error("[MCP] Failed to read package.json version", error);
52
- }
53
-
54
- // Start the server
55
- startServer();
56
-
57
- async function startServer() {
58
- const getArg = (name: string) => {
59
- const idx = args.indexOf(name);
60
- return idx !== -1 && args[idx + 1] ? args[idx + 1] : undefined;
61
- };
62
-
63
- const cliEnv = getArg("--env");
64
-
65
- if (cliEnv === "staging") {
66
- console.error("[MCP] Running in Staging environment");
67
- process.env.CYBERMEM_ENV = "staging";
68
- }
69
-
70
- // --- IMPLEMENTATION LOGIC ---
71
-
72
- let memory: IMemory | null = null;
73
- let sdk_update_memory:
74
- | ((
75
- id: string,
76
- content?: string,
77
- tags?: string[],
78
- metadata?: Record<string, unknown>,
79
- ) => Promise<unknown>)
80
- | null = null;
81
- let sdk_reinforce_memory:
82
- | ((id: string, boost?: number) => Promise<unknown>)
83
- | null = null;
36
+ } catch {}
84
37
 
85
- // LOCAL SDK MODE
86
- const dbPath = process.env.OM_DB_PATH!;
87
- const fs = await import("fs");
88
- const path = await import("path");
89
- try {
90
- const dir = path.dirname(dbPath);
91
- if (dir) fs.mkdirSync(dir, { recursive: true });
92
- } catch {}
38
+ const VALID_VERSION = (
39
+ PACKAGE_VERSION.match(/^\d+\.\d+\.\d+$/) ? PACKAGE_VERSION : "0.0.0"
40
+ ) as `${number}.${number}.${number}`;
93
41
 
42
+ const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
43
+ PROTOCOL:
44
+ 1. On session start: call query_memory("user context profile")
45
+ 2. Store insights immediately with add_memory
46
+ 3. Corrections: update_memory
47
+ 4. Decay prevention: reinforce_memory
48
+ Full protocol: https://docs.cybermem.dev/agent-protocol`;
49
+
50
+ // --- ERROR TRAPPING ---
51
+ process.on("uncaughtException", (err) => {
52
+ console.error("[CRITICAL] Uncaught Exception:", err);
53
+ });
54
+ process.on("unhandledRejection", (reason) => {
55
+ console.error("[CRITICAL] Unhandled Rejection:", reason);
56
+ });
57
+
58
+ // --- LOGGING ---
59
+ const logActivity = async (tool: string, status: number = 200) => {
94
60
  try {
95
- const { Memory } = await import("openmemory-js/dist/core/memory.js");
96
- const hsg = await import("openmemory-js/dist/memory/hsg.js");
97
- sdk_update_memory = hsg.update_memory;
98
- sdk_reinforce_memory = hsg.reinforce_memory;
99
- memory = new Memory() as IMemory;
61
+ const ctx = requestContext.getStore();
62
+ const client = ctx?.clientName || "unknown";
63
+ console.log(`[MCP-LOG] client=${client} tool=${tool} status=${status}`);
64
+ const ts = Date.now();
65
+ const isError = status >= 400 ? 1 : 0;
66
+
67
+ await run_async(
68
+ "INSERT INTO cybermem_access_log (timestamp, client_name, client_version, method, endpoint, tool, status, is_error) VALUES (?, ?, ?, 'POST', '/mcp', ?, ?, ?)",
69
+ [ts, client, PACKAGE_VERSION, tool, status.toString(), isError],
70
+ );
71
+ await run_async(
72
+ "INSERT INTO cybermem_stats (client_name, tool, count, errors, last_updated) VALUES (?, ?, 1, ?, ?) ON CONFLICT(client_name, tool) DO UPDATE SET count=count+1, errors=errors+?, last_updated=?",
73
+ [client, tool, isError, ts, isError, ts],
74
+ );
75
+ } catch (err: any) {
76
+ console.error("[MCP] Log Error:", err.message);
77
+ }
78
+ };
100
79
 
101
- // Initialize Tables
102
- const sqlite3 = await import("sqlite3");
103
- const db = new sqlite3.default.Database(dbPath);
104
- db.configure("busyTimeout", 5000);
105
- db.serialize(() => {
106
- db.run(
107
- "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));",
108
- );
109
- db.run(
110
- "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);",
111
- );
112
- db.run(
113
- "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);",
114
- );
115
- });
116
- db.close();
117
- } catch (e) {
118
- console.error("Failed to initialize OpenMemory SDK:", e);
80
+ // --- INITIALIZATION ---
81
+ async function initialize() {
82
+ const dbPath = process.env.OM_DB_PATH;
83
+ if (!dbPath) {
119
84
  console.error(
120
- "[FATAL] CyberMem cannot start without a working database. " +
121
- "Check OM_DB_PATH and ensure sqlite3 native bindings are installed.",
85
+ "[INIT] Environment variable OM_DB_PATH is not set. Please configure OM_DB_PATH to point to the OpenMemory database file (e.g., /path/to/openmemory.db).",
122
86
  );
123
87
  process.exit(1);
124
88
  }
125
89
 
126
- // PERSISTENT LOGGING DB
127
- let loggingDb: any = null;
128
- const initLoggingDb = async () => {
129
- if (loggingDb) return loggingDb;
130
- const dbPath = process.env.OM_DB_PATH!;
131
- const sqlite3 = await import("sqlite3");
132
- loggingDb = new sqlite3.default.Database(dbPath);
133
- loggingDb.configure("busyTimeout", 10000);
134
- return new Promise((resolve) => {
135
- loggingDb.serialize(() => {
136
- loggingDb.run("PRAGMA journal_mode=WAL;");
137
- loggingDb.run("PRAGMA synchronous=NORMAL;", () => resolve(loggingDb));
138
- });
139
- });
140
- };
141
-
142
- let stdioClientName: string | undefined = undefined;
90
+ const dir = dirname(dbPath);
91
+ if (dir && !existsSync(dir)) mkdirSync(dir, { recursive: true });
143
92
 
144
- // Protocol Instructions
145
- const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
146
- PROTOCOL:
147
- 1. On session start: call query_memory("user context profile")
148
- 2. Store new insights immediately with add_memory (STABLE data)
149
- 3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
150
- 4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
151
- 5. Always include tags: [topic, year, source:your-client-name]
152
- For full protocol: https://docs.cybermem.dev/agent-protocol`;
153
-
154
- const logActivity = async (
155
- operation: string,
156
- opts: {
157
- method?: string;
158
- endpoint?: string;
159
- status?: number;
160
- sessionId?: string;
161
- } = {},
162
- ) => {
163
- // Determine client name (priority: specific > store > default)
164
- let client: string;
165
- const ctx = requestContext.getStore();
166
-
167
- if (opts.sessionId) {
168
- // For SSE sessions, prefer the real client name from the request context when available
169
- client = ctx?.clientName || "sse-client";
170
- } else if (ctx) {
171
- client = ctx.clientName || stdioClientName || "unknown";
172
- } else {
173
- client = stdioClientName || "unknown";
174
- }
175
-
176
- const { method = "POST", endpoint = "/mcp", status = 200 } = opts;
177
- try {
178
- const db = await initLoggingDb();
179
- const ts = Date.now();
180
- const is_error = status >= 400 ? 1 : 0;
181
- db.serialize(() => {
182
- db.run(
183
- "INSERT INTO cybermem_access_log (timestamp, client_name, client_version, method, endpoint, operation, status, is_error) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
184
- [
185
- ts,
186
- client,
187
- PACKAGE_VERSION,
188
- method,
189
- endpoint,
190
- operation,
191
- status.toString(),
192
- is_error,
193
- ],
194
- );
195
- db.run(
196
- "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 = ?",
197
- [client, operation, is_error, ts, is_error, ts],
198
- );
199
- });
200
- } catch {}
201
- };
202
-
203
- // Factory to create configured McpServer instance
204
- const createConfiguredServer = (
205
- onClientConnected?: (name: string) => void,
206
- ) => {
207
- const server = new McpServer(
208
- { name: "cybermem", version: PACKAGE_VERSION },
209
- {
210
- instructions: CYBERMEM_INSTRUCTIONS,
211
- },
212
- );
213
-
214
- // access underlying server to set internal state for direct memory access
215
- // Casting to unknown first to allow adding private property
216
- (server as unknown as { _memoryReady: boolean })._memoryReady = true;
217
-
218
- server.registerResource(
219
- "CyberMem Agent Protocol",
220
- "cybermem://protocol",
221
- { description: "Instructions for AI agents", mimeType: "text/plain" },
222
- async () => ({
223
- contents: [
224
- {
225
- uri: "cybermem://protocol",
226
- mimeType: "text/plain",
227
- text: CYBERMEM_INSTRUCTIONS,
228
- },
229
- ],
230
- }),
93
+ // Migrations
94
+ try {
95
+ await run_async(
96
+ "CREATE TABLE IF NOT EXISTS cybermem_stats (id INTEGER PRIMARY KEY AUTOINCREMENT, client_name TEXT NOT NULL, tool TEXT NOT NULL, count INTEGER DEFAULT 0, errors INTEGER DEFAULT 0, last_updated INTEGER NOT NULL, UNIQUE(client_name, tool));",
231
97
  );
232
-
233
- // access underlying server
234
- server.server.setRequestHandler(
235
- InitializeRequestSchema,
236
- async (request) => {
237
- // For SSE multiple clients, stdioClientName global is less useful,
238
- // but we can set it for context if running in single-user mode.
239
- // For multi-user, rely on requestContext.
240
- // For SSE multiple clients, rely on per-request context instead of a global.
241
- const clientName = request.params.clientInfo.name;
242
- if (onClientConnected) {
243
- onClientConnected(clientName);
244
- } else {
245
- stdioClientName = clientName;
246
- }
247
- console.error(`[MCP] Client identified via handshake: ${clientName}`);
248
- return {
249
- protocolVersion: "2024-11-05",
250
- capabilities: {
251
- tools: { listChanged: true },
252
- resources: { subscribe: true },
253
- },
254
- serverInfo: {
255
- name: "cybermem",
256
- version: PACKAGE_VERSION,
257
- },
258
- };
259
- },
98
+ await run_async(
99
+ "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, tool TEXT NOT NULL, status TEXT NOT NULL, is_error INTEGER DEFAULT 0);",
260
100
  );
261
101
 
262
- // TOOLS
263
- server.registerTool(
264
- "add_memory",
265
- {
266
- description:
267
- "Store a new memory. Use for high-quality, stable data. " +
268
- CYBERMEM_INSTRUCTIONS,
269
- inputSchema: z.object({
270
- content: z.string(),
271
- tags: z.array(z.string()).optional(),
272
- }),
273
- },
274
- async (args: { content: string; tags?: string[] }) => {
275
- const res = await memory!.add(args.content, { tags: args.tags });
276
- await logActivity("create", {
277
- method: "POST",
278
- endpoint: "/memory/add",
279
- status: 200,
280
- });
281
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
282
- },
283
- );
284
-
285
- server.registerTool(
286
- "query_memory",
287
- {
288
- description: "Search memories.",
289
- inputSchema: z.object({ query: z.string(), k: z.number().default(5) }),
290
- },
291
- async (args: { query: string; k?: number }) => {
292
- const res = await memory!.search(args.query, { limit: args.k });
293
- await logActivity("read", {
294
- method: "POST",
295
- endpoint: "/memory/query",
296
- status: 200,
297
- });
298
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
299
- },
300
- );
102
+ // Robustly check for and rename 'operation' to 'tool'
103
+ const statsInfo = (await all_async(
104
+ "PRAGMA table_info(cybermem_stats);",
105
+ )) as any[];
106
+ if (statsInfo.some((col: any) => col.name === "operation")) {
107
+ await run_async(
108
+ "ALTER TABLE cybermem_stats RENAME COLUMN operation TO tool;",
109
+ );
110
+ } else if (!statsInfo.some((col: any) => col.name === "tool")) {
111
+ await run_async(
112
+ "ALTER TABLE cybermem_stats ADD COLUMN tool TEXT DEFAULT 'unknown';",
113
+ );
114
+ }
301
115
 
302
- server.registerTool(
303
- "update_memory",
304
- {
305
- description:
306
- "Mutate existing memory (content/tags). HIGH COST: re-embeds and re-links. Use for corrections.",
307
- inputSchema: z.object({
308
- id: z.string(),
309
- content: z.string().optional(),
310
- tags: z.array(z.string()).optional(),
311
- }),
312
- },
313
- async (args: { id: string; content?: string; tags?: string[] }) => {
314
- if (!sdk_update_memory) throw new Error("Update not available in SDK");
315
- if (args.content === undefined && args.tags === undefined) {
316
- throw new Error(
317
- "At least one of 'content' or 'tags' must be provided to update_memory",
318
- );
319
- }
320
- const res = await sdk_update_memory(args.id, args.content, args.tags);
321
- await logActivity("update", {
322
- method: "PATCH",
323
- endpoint: `/memory/${args.id}`,
324
- status: 200,
325
- });
326
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
327
- },
328
- );
116
+ const logInfo = (await all_async(
117
+ "PRAGMA table_info(cybermem_access_log);",
118
+ )) as any[];
119
+ if (logInfo.some((col: any) => col.name === "operation")) {
120
+ await run_async(
121
+ "ALTER TABLE cybermem_access_log RENAME COLUMN operation TO tool;",
122
+ );
123
+ } else if (!logInfo.some((col: any) => col.name === "tool")) {
124
+ await run_async(
125
+ "ALTER TABLE cybermem_access_log ADD COLUMN tool TEXT DEFAULT 'unknown';",
126
+ );
127
+ }
329
128
 
330
- server.registerTool(
331
- "reinforce_memory",
332
- {
333
- description:
334
- "Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
335
- inputSchema: z.object({
336
- id: z.string(),
337
- boost: z.number().default(0.1),
338
- }),
339
- },
340
- async (args: { id: string; boost?: number }) => {
341
- if (!sdk_reinforce_memory)
342
- throw new Error("Reinforce not available in SDK");
343
- const res = await sdk_reinforce_memory(args.id, args.boost);
344
- await logActivity("update", {
345
- method: "POST",
346
- endpoint: `/memory/${args.id}/reinforce`,
347
- status: 200,
348
- });
349
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
350
- },
129
+ // Backfill NULL tool values for SQLite safety
130
+ await run_async(
131
+ "UPDATE cybermem_access_log SET tool = 'unknown' WHERE tool IS NULL;",
351
132
  );
352
-
353
- server.registerTool(
354
- "delete_memory",
355
- {
356
- description: "Delete memory",
357
- inputSchema: z.object({ id: z.string() }),
358
- },
359
- async (args: { id: string }) => {
360
- const dbPath = process.env.OM_DB_PATH!;
361
- const sqlite3 = await import("sqlite3");
362
- const db = new sqlite3.default.Database(dbPath);
363
- return new Promise((resolve, reject) => {
364
- db.serialize(() => {
365
- db.run("DELETE FROM memories WHERE id = ?", [args.id]);
366
- db.run(
367
- "DELETE FROM vectors WHERE id = ?",
368
- [args.id],
369
- async (err: Error | null) => {
370
- db.close();
371
- await logActivity("delete", {
372
- method: "DELETE",
373
- endpoint: `/memory/${args.id}`,
374
- status: err ? 500 : 200,
375
- });
376
- if (err) reject(err);
377
- else resolve({ content: [{ type: "text", text: "Deleted" }] });
378
- },
379
- );
380
- });
381
- });
382
- },
133
+ await run_async(
134
+ "UPDATE cybermem_stats SET tool = 'unknown' WHERE tool IS NULL;",
383
135
  );
384
-
385
- return server;
386
- };
387
-
388
- // EXPRESS SERVER
389
- // HTTP server mode for Docker/Traefik deployment
390
- const useHttp = args.includes("--http") || args.includes("--port");
391
- if (useHttp) {
392
- const port = parseInt(getArg("--port") || "3100", 10);
393
- const app = express();
394
- app.use(cors());
395
- app.use((req, res, next) => {
396
- // Skip JSON parsing for SSE message endpoint - it needs raw body stream
397
- // Use req.url to handle query params like /message?sessionId=...
398
- if (req.url.startsWith("/message")) {
399
- return next();
400
- }
401
- express.json()(req, res, next);
402
- });
403
- app.get("/health", (req, res) =>
404
- res.json({ ok: true, version: PACKAGE_VERSION }),
136
+ } catch (e: any) {
137
+ console.error(
138
+ "[INIT] Migration Error: Failed to apply database migrations:",
139
+ e?.message ?? e,
405
140
  );
141
+ process.exit(1);
142
+ }
143
+ }
406
144
 
407
- app.use((req, res, next) => {
408
- const clientName =
409
- (req.headers["x-client-name"] as string) || "antigravity-client";
410
- requestContext.run({ clientName }, next);
411
- });
145
+ // --- SERVER SETUP ---
146
+ interface AuthContext {
147
+ clientName: string;
148
+ [key: string]: unknown;
149
+ }
412
150
 
413
- if (memory) {
414
- app.post("/add", async (req, res) => {
151
+ const server = new FastMCP<AuthContext>({
152
+ name: "cybermem",
153
+ version: VALID_VERSION,
154
+ instructions: CYBERMEM_INSTRUCTIONS,
155
+ health: { enabled: true, path: "/health" },
156
+ authenticate: async (req) => {
157
+ const clientName = (req.headers["x-client-name"] ||
158
+ req.headers["X-Client-Name"] ||
159
+ "unknown") as string;
160
+ // Extract versioned naming if present (e.g. "antigravity/v1.0.0" -> "antigravity")
161
+ return { clientName: clientName.split("/")[0] };
162
+ },
163
+ });
164
+
165
+ const memory = new Memory() as IMemory;
166
+
167
+ const app = server.getApp();
168
+ // Keep Hono middleware for custom routes if any, though FastMCP transport bypasses it
169
+ app.use("*", async (c, next) => {
170
+ const clientName = (
171
+ c.req.header("X-Client-Name") ||
172
+ c.req.header("x-client-name") ||
173
+ "unknown"
174
+ ).split("/")[0];
175
+ return requestContext.run({ clientName }, next);
176
+ });
177
+
178
+ // TOOLS
179
+ server.addTool({
180
+ name: "add_memory",
181
+ description: "Store a new memory with optional tags for semantic retrieval.",
182
+ parameters: z.object({
183
+ content: z.string().describe("The text content of the memory"),
184
+ tags: z.array(z.string()).optional().describe("Category tags"),
185
+ }),
186
+ execute: async (args, context) => {
187
+ return requestContext.run(
188
+ { clientName: context.session?.clientName },
189
+ async () => {
415
190
  try {
416
- const result = await (memory as any)!.add(req.body.content, {
417
- id: req.body.id,
418
- tags: req.body.tags,
419
- });
420
- await logActivity("create", {
421
- method: "POST",
422
- endpoint: "/add",
423
- status: 200,
424
- });
425
- res.json(result);
426
- } catch (e: any) {
427
- res.status(500).json({ error: e.message });
191
+ const res = await memory.add(args.content, { tags: args.tags });
192
+ await logActivity("add_memory");
193
+ return JSON.stringify(res);
194
+ } catch (err: any) {
195
+ await logActivity("add_memory", 500);
196
+ throw err;
428
197
  }
429
- });
430
- app.post("/query", async (req, res) => {
198
+ },
199
+ );
200
+ },
201
+ });
202
+
203
+ server.addTool({
204
+ name: "query_memory",
205
+ description: "Retrieve relevant memories using semantic search.",
206
+ parameters: z.object({
207
+ query: z.string().describe("Search query string"),
208
+ k: z.number().default(5).describe("Number of results"),
209
+ }),
210
+ execute: async (args, context) => {
211
+ return requestContext.run(
212
+ { clientName: context.session?.clientName },
213
+ async () => {
431
214
  try {
432
- const result = await memory!.search(req.body.query || "", {
433
- limit: req.body.k || 5,
434
- });
435
- await logActivity("read", {
436
- method: "POST",
437
- endpoint: "/query",
438
- status: 200,
439
- });
440
- res.json(result);
441
- } catch (e: any) {
442
- res.status(500).json({ error: e.message });
215
+ const res = await memory.search(args.query, { limit: args.k });
216
+ await logActivity("query_memory");
217
+ return JSON.stringify(res);
218
+ } catch (err: any) {
219
+ await logActivity("query_memory", 500);
220
+ throw err;
443
221
  }
444
- });
445
- app.get("/all", async (req, res) => {
222
+ },
223
+ );
224
+ },
225
+ });
226
+
227
+ server.addTool({
228
+ name: "update_memory",
229
+ description:
230
+ "Update an existing memory's content or tags. At least one must be provided.",
231
+ parameters: z
232
+ .object({
233
+ id: z.string().describe("Memory ID"),
234
+ content: z.string().optional().describe("New content"),
235
+ tags: z.array(z.string()).optional().describe("New tags"),
236
+ })
237
+ .refine((data) => data.content !== undefined || data.tags !== undefined, {
238
+ message: "Either content or tags must be provided for update",
239
+ path: ["content"],
240
+ }),
241
+ execute: async (args, context) => {
242
+ return requestContext.run(
243
+ { clientName: context.session?.clientName },
244
+ async () => {
446
245
  try {
447
- const result = await memory!.search("", { limit: 10 });
448
- await logActivity("read", {
449
- method: "GET",
450
- endpoint: "/all",
451
- status: 200,
452
- });
453
- res.json(result);
454
- } catch (e: any) {
455
- res.status(500).json({ error: e.message });
246
+ const res = await update_memory(args.id, args.content, args.tags);
247
+ await logActivity("update_memory");
248
+ return JSON.stringify(res);
249
+ } catch (err: any) {
250
+ await logActivity("update_memory", 500);
251
+ throw err;
456
252
  }
457
- });
458
- app.patch("/memory/:id", async (req, res) => {
253
+ },
254
+ );
255
+ },
256
+ });
257
+
258
+ server.addTool({
259
+ name: "reinforce_memory",
260
+ description: "Boost a memory's relevance score to prevent decay.",
261
+ parameters: z.object({
262
+ id: z.string().describe("Memory ID"),
263
+ boost: z
264
+ .number()
265
+ .default(0.1)
266
+ .describe("Relevance boost amount (0.0 to 1.0)"),
267
+ }),
268
+ execute: async (args, context) => {
269
+ return requestContext.run(
270
+ { clientName: context.session?.clientName },
271
+ async () => {
459
272
  try {
460
- const result = await sdk_update_memory(
461
- req.params.id,
462
- req.body.content,
463
- req.body.tags,
464
- req.body.metadata,
465
- );
466
- await logActivity("update", {
467
- method: "PATCH",
468
- endpoint: `/memory/${req.params.id}`,
469
- status: 200,
470
- });
471
- res.json(result);
472
- } catch (e: any) {
473
- res.status(500).json({ error: e.message });
273
+ await reinforce_memory(args.id, args.boost);
274
+ await logActivity("reinforce_memory");
275
+ return `Memory reinforced: ${args.id}`;
276
+ } catch (err: any) {
277
+ await logActivity("reinforce_memory", 500);
278
+ throw err;
474
279
  }
475
- });
476
- app.post("/memory/:id/reinforce", async (req, res) => {
280
+ },
281
+ );
282
+ },
283
+ });
284
+
285
+ server.addTool({
286
+ name: "delete_memory",
287
+ description: "Permanently delete a memory and its associated vectors.",
288
+ parameters: z.object({
289
+ id: z.string().describe("Memory ID"),
290
+ }),
291
+ execute: async (args, context) => {
292
+ return requestContext.run(
293
+ { clientName: context.session?.clientName },
294
+ async () => {
477
295
  try {
478
- await sdk_reinforce_memory(req.params.id, req.body.boost);
479
- await logActivity("update", {
480
- method: "POST",
481
- endpoint: `/memory/${req.params.id}/reinforce`,
482
- status: 200,
483
- });
484
- res.json({ ok: true });
485
- } catch (e: any) {
486
- res.status(500).json({ error: e.message });
296
+ await run_async("DELETE FROM memories WHERE id=?", [args.id]);
297
+ await run_async("DELETE FROM vectors WHERE id=?", [args.id]);
298
+ await logActivity("delete_memory");
299
+ return "Deleted";
300
+ } catch (err: any) {
301
+ await logActivity("delete_memory", 500);
302
+ throw err;
487
303
  }
488
- });
489
- app.delete("/memory/:id", async (req, res) => {
490
- const dbPath = process.env.OM_DB_PATH!;
491
- const sqlite3 = await import("sqlite3");
492
- const db = new sqlite3.default.Database(dbPath);
493
- db.run(
494
- "DELETE FROM memories WHERE id = ?",
495
- [req.params.id],
496
- async () => {
497
- db.close();
498
- await logActivity("delete", {
499
- method: "DELETE",
500
- endpoint: `/memory/${req.params.id}`,
501
- status: 200,
502
- });
503
- res.json({ ok: true });
504
- },
505
- );
506
- });
507
- }
508
-
509
- // MULTI-SESSION SSE SUPPORT
510
- const sessions = new Map<
511
- string,
512
- {
513
- server: McpServer;
514
- transport: SSEServerTransport;
515
- clientName?: string;
516
- }
517
- >();
518
-
519
- // Legacy MCP endpoint - 410 Gone
520
- app.all("/mcp", (req, res) => {
521
- res
522
- .status(410)
523
- .send(
524
- "Endpoint /mcp is deprecated. Please update your client configuration to use /sse for Server-Sent Events.",
525
- );
526
- });
527
-
528
- app.get("/sse", async (req, res) => {
529
- console.error("[MCP] Attempting SSE Connection...");
530
- const transport = new SSEServerTransport("/message", res);
531
- const newServer = createConfiguredServer((name) => {
532
- const session = sessions.get(transport.sessionId);
533
- if (session) session.clientName = name;
534
- });
535
-
536
- try {
537
- await newServer.connect(transport);
538
- sessions.set(transport.sessionId, { server: newServer, transport });
304
+ },
305
+ );
306
+ },
307
+ });
539
308
 
540
- transport.onclose = () => {
541
- console.error(`[MCP] SSE Connection Closed: ${transport.sessionId}`);
542
- sessions.delete(transport.sessionId);
543
- };
544
- transport.onerror = (err: Error) => {
545
- console.error(
546
- `[MCP] SSE Connection Error: ${transport.sessionId}`,
547
- err,
548
- );
549
- sessions.delete(transport.sessionId);
550
- };
309
+ // START
310
+ async function main() {
311
+ console.error("[INIT] Starting CyberMem MCP...");
312
+ await initialize();
313
+ console.error("[INIT] Database initialized.");
551
314
 
552
- // await transport.start(); // FIXED: connect() starts it automatically
553
- } catch (err) {
554
- console.error("[MCP] Failed to start SSE transport:", err);
555
- sessions.delete(transport.sessionId);
556
- // If headers haven't been sent, send 500
557
- if (!res.headersSent) {
558
- res.status(500).send("Internal Server Error during SSE handshake");
559
- }
560
- }
561
- });
315
+ const argsArr = process.argv.slice(2);
316
+ const getArg = (name: string) => {
317
+ const idx = argsArr.indexOf(name);
318
+ return idx !== -1 ? argsArr[idx + 1] : undefined;
319
+ };
562
320
 
563
- app.post("/message", async (req, res) => {
564
- const sessionId = req.query.sessionId as string;
565
- const session = sessions.get(sessionId);
566
- if (!session) {
567
- res.status(404).send("Session not found");
568
- return;
569
- }
570
- try {
571
- const clientName = session.clientName || "sse-client";
572
- await requestContext.run({ clientName }, async () => {
573
- await session.transport.handlePostMessage(req, res);
574
- });
575
- } catch (err) {
576
- console.error(
577
- `[MCP] Error handling message for session ${sessionId}:`,
578
- err,
579
- );
580
- if (!res.headersSent) {
581
- res.status(500).send("Internal Server Error processing message");
321
+ const port = parseInt(getArg("--port") || "3100", 10);
322
+ const useHttp = argsArr.includes("--http") || argsArr.includes("--port");
323
+
324
+ console.error(`[INIT] Starting ${useHttp ? "HTTP" : "STDIO"} server...`);
325
+ await server.start({
326
+ transportType: useHttp ? "httpStream" : "stdio",
327
+ httpStream: useHttp
328
+ ? {
329
+ port,
330
+ host: "0.0.0.0",
331
+ endpoint: "/mcp",
332
+ stateless: false,
333
+ enableJsonResponse: true,
582
334
  }
583
- }
584
- });
335
+ : undefined,
336
+ });
585
337
 
586
- app.listen(port, () =>
587
- console.error(`CyberMem MCP running on http://localhost:${port}`),
588
- );
338
+ if (useHttp) {
339
+ console.error(`CyberMem MCP running on http://localhost:${port}/mcp`);
589
340
  } else {
590
- // STDIO
591
- const transport = new StdioServerTransport();
592
- const server = createConfiguredServer();
593
- server
594
- .connect(transport)
595
- .then(() => console.error("CyberMem MCP connected via STDIO"));
341
+ console.error(`CyberMem MCP ${VALID_VERSION} [STDIO]`);
596
342
  }
597
343
  }
344
+
345
+ main().catch((err) => {
346
+ console.error("[CRITICAL] Main Failure:", err);
347
+ process.exit(1);
348
+ });