@cybermem/mcp 0.14.15 → 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/CHANGELOG.md +6 -0
- package/dist/index.js +219 -413
- package/e2e/api.spec.ts +132 -186
- package/e2e/sse_transport.spec.ts +69 -29
- package/e2e/sse_transport_multi.spec.ts +73 -137
- package/e2e/utils/FastMCPHandshakeTransport.ts +183 -0
- package/package.json +6 -9
- package/scripts/postbuild.js +24 -0
- package/src/index.ts +292 -541
- package/src/openmemory-js.d.ts +6 -0
- package/test-handshake.ts +26 -0
package/dist/index.js
CHANGED
|
@@ -1,458 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
4
|
require("./console-fix.js");
|
|
7
5
|
require("./env.js");
|
|
8
|
-
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9
|
-
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
10
|
-
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
11
|
-
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
12
6
|
const async_hooks_1 = require("async_hooks");
|
|
13
|
-
const
|
|
14
|
-
const express_1 = __importDefault(require("express"));
|
|
7
|
+
const fastmcp_1 = require("fastmcp");
|
|
15
8
|
const fs_1 = require("fs");
|
|
16
9
|
const path_1 = require("path");
|
|
17
10
|
const zod_1 = require("zod");
|
|
18
|
-
//
|
|
11
|
+
// --- CORE SDK IMPORTS ---
|
|
12
|
+
const db_js_1 = require("openmemory-js/dist/core/db.js");
|
|
13
|
+
const memory_js_1 = require("openmemory-js/dist/core/memory.js");
|
|
14
|
+
const hsg_js_1 = require("openmemory-js/dist/memory/hsg.js");
|
|
15
|
+
// --- GLOBALS & CONTEXT ---
|
|
19
16
|
const requestContext = new async_hooks_1.AsyncLocalStorage();
|
|
20
|
-
// CLI args processing
|
|
21
|
-
const args = process.argv.slice(2);
|
|
22
|
-
// Read version from package.json
|
|
23
17
|
let PACKAGE_VERSION = "0.0.0";
|
|
24
18
|
try {
|
|
25
19
|
const packageJsonPath = (0, path_1.join)(__dirname, "../package.json");
|
|
26
20
|
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, "utf-8"));
|
|
27
21
|
PACKAGE_VERSION = packageJson.version;
|
|
28
22
|
}
|
|
29
|
-
catch
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
let sdk_reinforce_memory = null;
|
|
48
|
-
// LOCAL SDK MODE
|
|
49
|
-
const dbPath = process.env.OM_DB_PATH;
|
|
50
|
-
const fs = await import("fs");
|
|
51
|
-
const path = await import("path");
|
|
23
|
+
catch { }
|
|
24
|
+
const VALID_VERSION = (PACKAGE_VERSION.match(/^\d+\.\d+\.\d+$/) ? PACKAGE_VERSION : "0.0.0");
|
|
25
|
+
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
26
|
+
PROTOCOL:
|
|
27
|
+
1. On session start: call query_memory("user context profile")
|
|
28
|
+
2. Store insights immediately with add_memory
|
|
29
|
+
3. Corrections: update_memory
|
|
30
|
+
4. Decay prevention: reinforce_memory
|
|
31
|
+
Full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
32
|
+
// --- ERROR TRAPPING ---
|
|
33
|
+
process.on("uncaughtException", (err) => {
|
|
34
|
+
console.error("[CRITICAL] Uncaught Exception:", err);
|
|
35
|
+
});
|
|
36
|
+
process.on("unhandledRejection", (reason) => {
|
|
37
|
+
console.error("[CRITICAL] Unhandled Rejection:", reason);
|
|
38
|
+
});
|
|
39
|
+
// --- LOGGING ---
|
|
40
|
+
const logActivity = async (tool, status = 200) => {
|
|
52
41
|
try {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
const ctx = requestContext.getStore();
|
|
43
|
+
const client = ctx?.clientName || "unknown";
|
|
44
|
+
console.log(`[MCP-LOG] client=${client} tool=${tool} status=${status}`);
|
|
45
|
+
const ts = Date.now();
|
|
46
|
+
const isError = status >= 400 ? 1 : 0;
|
|
47
|
+
await (0, db_js_1.run_async)("INSERT INTO cybermem_access_log (timestamp, client_name, client_version, method, endpoint, tool, status, is_error) VALUES (?, ?, ?, 'POST', '/mcp', ?, ?, ?)", [ts, client, PACKAGE_VERSION, tool, status.toString(), isError]);
|
|
48
|
+
await (0, db_js_1.run_async)("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=?", [client, tool, isError, ts, isError, ts]);
|
|
56
49
|
}
|
|
57
|
-
catch {
|
|
58
|
-
|
|
59
|
-
const { Memory } = await import("openmemory-js/dist/core/memory.js");
|
|
60
|
-
const hsg = await import("openmemory-js/dist/memory/hsg.js");
|
|
61
|
-
sdk_update_memory = hsg.update_memory;
|
|
62
|
-
sdk_reinforce_memory = hsg.reinforce_memory;
|
|
63
|
-
memory = new Memory();
|
|
64
|
-
// Initialize Tables
|
|
65
|
-
const sqlite3 = await import("sqlite3");
|
|
66
|
-
const db = new sqlite3.default.Database(dbPath);
|
|
67
|
-
db.configure("busyTimeout", 5000);
|
|
68
|
-
db.serialize(() => {
|
|
69
|
-
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));");
|
|
70
|
-
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);");
|
|
71
|
-
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);");
|
|
72
|
-
});
|
|
73
|
-
db.close();
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error("[MCP] Log Error:", err.message);
|
|
74
52
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
53
|
+
};
|
|
54
|
+
// --- INITIALIZATION ---
|
|
55
|
+
async function initialize() {
|
|
56
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
57
|
+
if (!dbPath) {
|
|
58
|
+
console.error("[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).");
|
|
79
59
|
process.exit(1);
|
|
80
60
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
loggingDb.run("PRAGMA journal_mode=WAL;");
|
|
93
|
-
loggingDb.run("PRAGMA synchronous=NORMAL;", () => resolve(loggingDb));
|
|
94
|
-
});
|
|
95
|
-
});
|
|
96
|
-
};
|
|
97
|
-
let stdioClientName = undefined;
|
|
98
|
-
// Protocol Instructions
|
|
99
|
-
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
100
|
-
PROTOCOL:
|
|
101
|
-
1. On session start: call query_memory("user context profile")
|
|
102
|
-
2. Store new insights immediately with add_memory (STABLE data)
|
|
103
|
-
3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
|
|
104
|
-
4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
|
|
105
|
-
5. Always include tags: [topic, year, source:your-client-name]
|
|
106
|
-
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
107
|
-
const logActivity = async (operation, opts = {}) => {
|
|
108
|
-
// Determine client name (priority: specific > store > default)
|
|
109
|
-
let client;
|
|
110
|
-
const ctx = requestContext.getStore();
|
|
111
|
-
if (opts.sessionId) {
|
|
112
|
-
// For SSE sessions, prefer the real client name from the request context when available
|
|
113
|
-
client = ctx?.clientName || "sse-client";
|
|
61
|
+
const dir = (0, path_1.dirname)(dbPath);
|
|
62
|
+
if (dir && !(0, fs_1.existsSync)(dir))
|
|
63
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
64
|
+
// Migrations
|
|
65
|
+
try {
|
|
66
|
+
await (0, db_js_1.run_async)("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));");
|
|
67
|
+
await (0, db_js_1.run_async)("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);");
|
|
68
|
+
// Robustly check for and rename 'operation' to 'tool'
|
|
69
|
+
const statsInfo = (await (0, db_js_1.all_async)("PRAGMA table_info(cybermem_stats);"));
|
|
70
|
+
if (statsInfo.some((col) => col.name === "operation")) {
|
|
71
|
+
await (0, db_js_1.run_async)("ALTER TABLE cybermem_stats RENAME COLUMN operation TO tool;");
|
|
114
72
|
}
|
|
115
|
-
else if (
|
|
116
|
-
|
|
73
|
+
else if (!statsInfo.some((col) => col.name === "tool")) {
|
|
74
|
+
await (0, db_js_1.run_async)("ALTER TABLE cybermem_stats ADD COLUMN tool TEXT DEFAULT 'unknown';");
|
|
117
75
|
}
|
|
118
|
-
|
|
119
|
-
|
|
76
|
+
const logInfo = (await (0, db_js_1.all_async)("PRAGMA table_info(cybermem_access_log);"));
|
|
77
|
+
if (logInfo.some((col) => col.name === "operation")) {
|
|
78
|
+
await (0, db_js_1.run_async)("ALTER TABLE cybermem_access_log RENAME COLUMN operation TO tool;");
|
|
120
79
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const db = await initLoggingDb();
|
|
124
|
-
const ts = Date.now();
|
|
125
|
-
const is_error = status >= 400 ? 1 : 0;
|
|
126
|
-
db.serialize(() => {
|
|
127
|
-
db.run("INSERT INTO cybermem_access_log (timestamp, client_name, client_version, method, endpoint, operation, status, is_error) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [
|
|
128
|
-
ts,
|
|
129
|
-
client,
|
|
130
|
-
PACKAGE_VERSION,
|
|
131
|
-
method,
|
|
132
|
-
endpoint,
|
|
133
|
-
operation,
|
|
134
|
-
status.toString(),
|
|
135
|
-
is_error,
|
|
136
|
-
]);
|
|
137
|
-
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]);
|
|
138
|
-
});
|
|
80
|
+
else if (!logInfo.some((col) => col.name === "tool")) {
|
|
81
|
+
await (0, db_js_1.run_async)("ALTER TABLE cybermem_access_log ADD COLUMN tool TEXT DEFAULT 'unknown';");
|
|
139
82
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
83
|
+
// Backfill NULL tool values for SQLite safety
|
|
84
|
+
await (0, db_js_1.run_async)("UPDATE cybermem_access_log SET tool = 'unknown' WHERE tool IS NULL;");
|
|
85
|
+
await (0, db_js_1.run_async)("UPDATE cybermem_stats SET tool = 'unknown' WHERE tool IS NULL;");
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
console.error("[INIT] Migration Error: Failed to apply database migrations:", e?.message ?? e);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const server = new fastmcp_1.FastMCP({
|
|
93
|
+
name: "cybermem",
|
|
94
|
+
version: VALID_VERSION,
|
|
95
|
+
instructions: CYBERMEM_INSTRUCTIONS,
|
|
96
|
+
health: { enabled: true, path: "/health" },
|
|
97
|
+
authenticate: async (req) => {
|
|
98
|
+
const clientName = (req.headers["x-client-name"] ||
|
|
99
|
+
req.headers["X-Client-Name"] ||
|
|
100
|
+
"unknown");
|
|
101
|
+
// Extract versioned naming if present (e.g. "antigravity/v1.0.0" -> "antigravity")
|
|
102
|
+
return { clientName: clientName.split("/")[0] };
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
const memory = new memory_js_1.Memory();
|
|
106
|
+
const app = server.getApp();
|
|
107
|
+
// Keep Hono middleware for custom routes if any, though FastMCP transport bypasses it
|
|
108
|
+
app.use("*", async (c, next) => {
|
|
109
|
+
const clientName = (c.req.header("X-Client-Name") ||
|
|
110
|
+
c.req.header("x-client-name") ||
|
|
111
|
+
"unknown").split("/")[0];
|
|
112
|
+
return requestContext.run({ clientName }, next);
|
|
113
|
+
});
|
|
114
|
+
// TOOLS
|
|
115
|
+
server.addTool({
|
|
116
|
+
name: "add_memory",
|
|
117
|
+
description: "Store a new memory with optional tags for semantic retrieval.",
|
|
118
|
+
parameters: zod_1.z.object({
|
|
119
|
+
content: zod_1.z.string().describe("The text content of the memory"),
|
|
120
|
+
tags: zod_1.z.array(zod_1.z.string()).optional().describe("Category tags"),
|
|
121
|
+
}),
|
|
122
|
+
execute: async (args, context) => {
|
|
123
|
+
return requestContext.run({ clientName: context.session?.clientName }, async () => {
|
|
124
|
+
try {
|
|
125
|
+
const res = await memory.add(args.content, { tags: args.tags });
|
|
126
|
+
await logActivity("add_memory");
|
|
127
|
+
return JSON.stringify(res);
|
|
168
128
|
}
|
|
169
|
-
|
|
170
|
-
|
|
129
|
+
catch (err) {
|
|
130
|
+
await logActivity("add_memory", 500);
|
|
131
|
+
throw err;
|
|
171
132
|
}
|
|
172
|
-
console.error(`[MCP] Client identified via handshake: ${clientName}`);
|
|
173
|
-
return {
|
|
174
|
-
protocolVersion: "2024-11-05",
|
|
175
|
-
capabilities: {
|
|
176
|
-
tools: { listChanged: true },
|
|
177
|
-
resources: { subscribe: true },
|
|
178
|
-
},
|
|
179
|
-
serverInfo: {
|
|
180
|
-
name: "cybermem",
|
|
181
|
-
version: PACKAGE_VERSION,
|
|
182
|
-
},
|
|
183
|
-
};
|
|
184
|
-
});
|
|
185
|
-
// TOOLS
|
|
186
|
-
server.registerTool("add_memory", {
|
|
187
|
-
description: "Store a new memory. Use for high-quality, stable data. " +
|
|
188
|
-
CYBERMEM_INSTRUCTIONS,
|
|
189
|
-
inputSchema: zod_1.z.object({
|
|
190
|
-
content: zod_1.z.string(),
|
|
191
|
-
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
192
|
-
}),
|
|
193
|
-
}, async (args) => {
|
|
194
|
-
const res = await memory.add(args.content, { tags: args.tags });
|
|
195
|
-
await logActivity("create", {
|
|
196
|
-
method: "POST",
|
|
197
|
-
endpoint: "/memory/add",
|
|
198
|
-
status: 200,
|
|
199
|
-
});
|
|
200
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
201
|
-
});
|
|
202
|
-
server.registerTool("query_memory", {
|
|
203
|
-
description: "Search memories.",
|
|
204
|
-
inputSchema: zod_1.z.object({ query: zod_1.z.string(), k: zod_1.z.number().default(5) }),
|
|
205
|
-
}, async (args) => {
|
|
206
|
-
const res = await memory.search(args.query, { limit: args.k });
|
|
207
|
-
await logActivity("read", {
|
|
208
|
-
method: "POST",
|
|
209
|
-
endpoint: "/memory/query",
|
|
210
|
-
status: 200,
|
|
211
|
-
});
|
|
212
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
213
133
|
});
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
server.addTool({
|
|
137
|
+
name: "query_memory",
|
|
138
|
+
description: "Retrieve relevant memories using semantic search.",
|
|
139
|
+
parameters: zod_1.z.object({
|
|
140
|
+
query: zod_1.z.string().describe("Search query string"),
|
|
141
|
+
k: zod_1.z.number().default(5).describe("Number of results"),
|
|
142
|
+
}),
|
|
143
|
+
execute: async (args, context) => {
|
|
144
|
+
return requestContext.run({ clientName: context.session?.clientName }, async () => {
|
|
145
|
+
try {
|
|
146
|
+
const res = await memory.search(args.query, { limit: args.k });
|
|
147
|
+
await logActivity("query_memory");
|
|
148
|
+
return JSON.stringify(res);
|
|
226
149
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
endpoint: `/memory/${args.id}`,
|
|
231
|
-
status: 200,
|
|
232
|
-
});
|
|
233
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
234
|
-
});
|
|
235
|
-
server.registerTool("reinforce_memory", {
|
|
236
|
-
description: "Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
|
|
237
|
-
inputSchema: zod_1.z.object({
|
|
238
|
-
id: zod_1.z.string(),
|
|
239
|
-
boost: zod_1.z.number().default(0.1),
|
|
240
|
-
}),
|
|
241
|
-
}, async (args) => {
|
|
242
|
-
if (!sdk_reinforce_memory)
|
|
243
|
-
throw new Error("Reinforce not available in SDK");
|
|
244
|
-
const res = await sdk_reinforce_memory(args.id, args.boost);
|
|
245
|
-
await logActivity("update", {
|
|
246
|
-
method: "POST",
|
|
247
|
-
endpoint: `/memory/${args.id}/reinforce`,
|
|
248
|
-
status: 200,
|
|
249
|
-
});
|
|
250
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
251
|
-
});
|
|
252
|
-
server.registerTool("delete_memory", {
|
|
253
|
-
description: "Delete memory",
|
|
254
|
-
inputSchema: zod_1.z.object({ id: zod_1.z.string() }),
|
|
255
|
-
}, async (args) => {
|
|
256
|
-
const dbPath = process.env.OM_DB_PATH;
|
|
257
|
-
const sqlite3 = await import("sqlite3");
|
|
258
|
-
const db = new sqlite3.default.Database(dbPath);
|
|
259
|
-
return new Promise((resolve, reject) => {
|
|
260
|
-
db.serialize(() => {
|
|
261
|
-
db.run("DELETE FROM memories WHERE id = ?", [args.id]);
|
|
262
|
-
db.run("DELETE FROM vectors WHERE id = ?", [args.id], async (err) => {
|
|
263
|
-
db.close();
|
|
264
|
-
await logActivity("delete", {
|
|
265
|
-
method: "DELETE",
|
|
266
|
-
endpoint: `/memory/${args.id}`,
|
|
267
|
-
status: err ? 500 : 200,
|
|
268
|
-
});
|
|
269
|
-
if (err)
|
|
270
|
-
reject(err);
|
|
271
|
-
else
|
|
272
|
-
resolve({ content: [{ type: "text", text: "Deleted" }] });
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
return server;
|
|
278
|
-
};
|
|
279
|
-
// EXPRESS SERVER
|
|
280
|
-
// HTTP server mode for Docker/Traefik deployment
|
|
281
|
-
const useHttp = args.includes("--http") || args.includes("--port");
|
|
282
|
-
if (useHttp) {
|
|
283
|
-
const port = parseInt(getArg("--port") || "3100", 10);
|
|
284
|
-
const app = (0, express_1.default)();
|
|
285
|
-
app.use((0, cors_1.default)());
|
|
286
|
-
app.use((req, res, next) => {
|
|
287
|
-
// Skip JSON parsing for SSE message endpoint - it needs raw body stream
|
|
288
|
-
// Use req.url to handle query params like /message?sessionId=...
|
|
289
|
-
if (req.url.startsWith("/message")) {
|
|
290
|
-
return next();
|
|
150
|
+
catch (err) {
|
|
151
|
+
await logActivity("query_memory", 500);
|
|
152
|
+
throw err;
|
|
291
153
|
}
|
|
292
|
-
express_1.default.json()(req, res, next);
|
|
293
|
-
});
|
|
294
|
-
app.get("/health", (req, res) => res.json({ ok: true, version: PACKAGE_VERSION }));
|
|
295
|
-
app.use((req, res, next) => {
|
|
296
|
-
const clientName = req.headers["x-client-name"] || "antigravity-client";
|
|
297
|
-
requestContext.run({ clientName }, next);
|
|
298
|
-
});
|
|
299
|
-
if (memory) {
|
|
300
|
-
app.post("/add", async (req, res) => {
|
|
301
|
-
try {
|
|
302
|
-
const result = await memory.add(req.body.content, {
|
|
303
|
-
id: req.body.id,
|
|
304
|
-
tags: req.body.tags,
|
|
305
|
-
});
|
|
306
|
-
await logActivity("create", {
|
|
307
|
-
method: "POST",
|
|
308
|
-
endpoint: "/add",
|
|
309
|
-
status: 200,
|
|
310
|
-
});
|
|
311
|
-
res.json(result);
|
|
312
|
-
}
|
|
313
|
-
catch (e) {
|
|
314
|
-
res.status(500).json({ error: e.message });
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
app.post("/query", async (req, res) => {
|
|
318
|
-
try {
|
|
319
|
-
const result = await memory.search(req.body.query || "", {
|
|
320
|
-
limit: req.body.k || 5,
|
|
321
|
-
});
|
|
322
|
-
await logActivity("read", {
|
|
323
|
-
method: "POST",
|
|
324
|
-
endpoint: "/query",
|
|
325
|
-
status: 200,
|
|
326
|
-
});
|
|
327
|
-
res.json(result);
|
|
328
|
-
}
|
|
329
|
-
catch (e) {
|
|
330
|
-
res.status(500).json({ error: e.message });
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
app.get("/all", async (req, res) => {
|
|
334
|
-
try {
|
|
335
|
-
const result = await memory.search("", { limit: 10 });
|
|
336
|
-
await logActivity("read", {
|
|
337
|
-
method: "GET",
|
|
338
|
-
endpoint: "/all",
|
|
339
|
-
status: 200,
|
|
340
|
-
});
|
|
341
|
-
res.json(result);
|
|
342
|
-
}
|
|
343
|
-
catch (e) {
|
|
344
|
-
res.status(500).json({ error: e.message });
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
app.patch("/memory/:id", async (req, res) => {
|
|
348
|
-
try {
|
|
349
|
-
const result = await sdk_update_memory(req.params.id, req.body.content, req.body.tags, req.body.metadata);
|
|
350
|
-
await logActivity("update", {
|
|
351
|
-
method: "PATCH",
|
|
352
|
-
endpoint: `/memory/${req.params.id}`,
|
|
353
|
-
status: 200,
|
|
354
|
-
});
|
|
355
|
-
res.json(result);
|
|
356
|
-
}
|
|
357
|
-
catch (e) {
|
|
358
|
-
res.status(500).json({ error: e.message });
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
app.post("/memory/:id/reinforce", async (req, res) => {
|
|
362
|
-
try {
|
|
363
|
-
await sdk_reinforce_memory(req.params.id, req.body.boost);
|
|
364
|
-
await logActivity("update", {
|
|
365
|
-
method: "POST",
|
|
366
|
-
endpoint: `/memory/${req.params.id}/reinforce`,
|
|
367
|
-
status: 200,
|
|
368
|
-
});
|
|
369
|
-
res.json({ ok: true });
|
|
370
|
-
}
|
|
371
|
-
catch (e) {
|
|
372
|
-
res.status(500).json({ error: e.message });
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
app.delete("/memory/:id", async (req, res) => {
|
|
376
|
-
const dbPath = process.env.OM_DB_PATH;
|
|
377
|
-
const sqlite3 = await import("sqlite3");
|
|
378
|
-
const db = new sqlite3.default.Database(dbPath);
|
|
379
|
-
db.run("DELETE FROM memories WHERE id = ?", [req.params.id], async () => {
|
|
380
|
-
db.close();
|
|
381
|
-
await logActivity("delete", {
|
|
382
|
-
method: "DELETE",
|
|
383
|
-
endpoint: `/memory/${req.params.id}`,
|
|
384
|
-
status: 200,
|
|
385
|
-
});
|
|
386
|
-
res.json({ ok: true });
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
// MULTI-SESSION SSE SUPPORT
|
|
391
|
-
const sessions = new Map();
|
|
392
|
-
// Legacy MCP endpoint - 410 Gone
|
|
393
|
-
app.all("/mcp", (req, res) => {
|
|
394
|
-
res
|
|
395
|
-
.status(410)
|
|
396
|
-
.send("Endpoint /mcp is deprecated. Please update your client configuration to use /sse for Server-Sent Events.");
|
|
397
154
|
});
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
server.addTool({
|
|
158
|
+
name: "update_memory",
|
|
159
|
+
description: "Update an existing memory's content or tags. At least one must be provided.",
|
|
160
|
+
parameters: zod_1.z
|
|
161
|
+
.object({
|
|
162
|
+
id: zod_1.z.string().describe("Memory ID"),
|
|
163
|
+
content: zod_1.z.string().optional().describe("New content"),
|
|
164
|
+
tags: zod_1.z.array(zod_1.z.string()).optional().describe("New tags"),
|
|
165
|
+
})
|
|
166
|
+
.refine((data) => data.content !== undefined || data.tags !== undefined, {
|
|
167
|
+
message: "Either content or tags must be provided for update",
|
|
168
|
+
path: ["content"],
|
|
169
|
+
}),
|
|
170
|
+
execute: async (args, context) => {
|
|
171
|
+
return requestContext.run({ clientName: context.session?.clientName }, async () => {
|
|
406
172
|
try {
|
|
407
|
-
await
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
console.error(`[MCP] SSE Connection Closed: ${transport.sessionId}`);
|
|
411
|
-
sessions.delete(transport.sessionId);
|
|
412
|
-
};
|
|
413
|
-
transport.onerror = (err) => {
|
|
414
|
-
console.error(`[MCP] SSE Connection Error: ${transport.sessionId}`, err);
|
|
415
|
-
sessions.delete(transport.sessionId);
|
|
416
|
-
};
|
|
417
|
-
// await transport.start(); // FIXED: connect() starts it automatically
|
|
173
|
+
const res = await (0, hsg_js_1.update_memory)(args.id, args.content, args.tags);
|
|
174
|
+
await logActivity("update_memory");
|
|
175
|
+
return JSON.stringify(res);
|
|
418
176
|
}
|
|
419
177
|
catch (err) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
// If headers haven't been sent, send 500
|
|
423
|
-
if (!res.headersSent) {
|
|
424
|
-
res.status(500).send("Internal Server Error during SSE handshake");
|
|
425
|
-
}
|
|
178
|
+
await logActivity("update_memory", 500);
|
|
179
|
+
throw err;
|
|
426
180
|
}
|
|
427
181
|
});
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
server.addTool({
|
|
185
|
+
name: "reinforce_memory",
|
|
186
|
+
description: "Boost a memory's relevance score to prevent decay.",
|
|
187
|
+
parameters: zod_1.z.object({
|
|
188
|
+
id: zod_1.z.string().describe("Memory ID"),
|
|
189
|
+
boost: zod_1.z
|
|
190
|
+
.number()
|
|
191
|
+
.default(0.1)
|
|
192
|
+
.describe("Relevance boost amount (0.0 to 1.0)"),
|
|
193
|
+
}),
|
|
194
|
+
execute: async (args, context) => {
|
|
195
|
+
return requestContext.run({ clientName: context.session?.clientName }, async () => {
|
|
196
|
+
try {
|
|
197
|
+
await (0, hsg_js_1.reinforce_memory)(args.id, args.boost);
|
|
198
|
+
await logActivity("reinforce_memory");
|
|
199
|
+
return `Memory reinforced: ${args.id}`;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
await logActivity("reinforce_memory", 500);
|
|
203
|
+
throw err;
|
|
434
204
|
}
|
|
205
|
+
});
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
server.addTool({
|
|
209
|
+
name: "delete_memory",
|
|
210
|
+
description: "Permanently delete a memory and its associated vectors.",
|
|
211
|
+
parameters: zod_1.z.object({
|
|
212
|
+
id: zod_1.z.string().describe("Memory ID"),
|
|
213
|
+
}),
|
|
214
|
+
execute: async (args, context) => {
|
|
215
|
+
return requestContext.run({ clientName: context.session?.clientName }, async () => {
|
|
435
216
|
try {
|
|
436
|
-
|
|
437
|
-
await
|
|
438
|
-
|
|
439
|
-
|
|
217
|
+
await (0, db_js_1.run_async)("DELETE FROM memories WHERE id=?", [args.id]);
|
|
218
|
+
await (0, db_js_1.run_async)("DELETE FROM vectors WHERE id=?", [args.id]);
|
|
219
|
+
await logActivity("delete_memory");
|
|
220
|
+
return "Deleted";
|
|
440
221
|
}
|
|
441
222
|
catch (err) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
res.status(500).send("Internal Server Error processing message");
|
|
445
|
-
}
|
|
223
|
+
await logActivity("delete_memory", 500);
|
|
224
|
+
throw err;
|
|
446
225
|
}
|
|
447
226
|
});
|
|
448
|
-
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
// START
|
|
230
|
+
async function main() {
|
|
231
|
+
console.error("[INIT] Starting CyberMem MCP...");
|
|
232
|
+
await initialize();
|
|
233
|
+
console.error("[INIT] Database initialized.");
|
|
234
|
+
const argsArr = process.argv.slice(2);
|
|
235
|
+
const getArg = (name) => {
|
|
236
|
+
const idx = argsArr.indexOf(name);
|
|
237
|
+
return idx !== -1 ? argsArr[idx + 1] : undefined;
|
|
238
|
+
};
|
|
239
|
+
const port = parseInt(getArg("--port") || "3100", 10);
|
|
240
|
+
const useHttp = argsArr.includes("--http") || argsArr.includes("--port");
|
|
241
|
+
console.error(`[INIT] Starting ${useHttp ? "HTTP" : "STDIO"} server...`);
|
|
242
|
+
await server.start({
|
|
243
|
+
transportType: useHttp ? "httpStream" : "stdio",
|
|
244
|
+
httpStream: useHttp
|
|
245
|
+
? {
|
|
246
|
+
port,
|
|
247
|
+
host: "0.0.0.0",
|
|
248
|
+
endpoint: "/mcp",
|
|
249
|
+
stateless: false,
|
|
250
|
+
enableJsonResponse: true,
|
|
251
|
+
}
|
|
252
|
+
: undefined,
|
|
253
|
+
});
|
|
254
|
+
if (useHttp) {
|
|
255
|
+
console.error(`CyberMem MCP running on http://localhost:${port}/mcp`);
|
|
449
256
|
}
|
|
450
257
|
else {
|
|
451
|
-
|
|
452
|
-
const transport = new stdio_js_1.StdioServerTransport();
|
|
453
|
-
const server = createConfiguredServer();
|
|
454
|
-
server
|
|
455
|
-
.connect(transport)
|
|
456
|
-
.then(() => console.error("CyberMem MCP connected via STDIO"));
|
|
258
|
+
console.error(`CyberMem MCP ${VALID_VERSION} [STDIO]`);
|
|
457
259
|
}
|
|
458
260
|
}
|
|
261
|
+
main().catch((err) => {
|
|
262
|
+
console.error("[CRITICAL] Main Failure:", err);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
});
|