@cybermem/mcp 0.14.5 → 0.14.8
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 +19 -0
- package/dist/index.js +198 -134
- package/package.json +1 -1
- package/src/index.ts +291 -199
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# @cybermem/mcp
|
|
2
2
|
|
|
3
|
+
## 0.14.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Automated patch version bump.
|
|
8
|
+
|
|
9
|
+
## 0.14.7
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [#114](https://github.com/mikhailkogan17/cybermem/pull/114) [`7871ba9`](https://github.com/mikhailkogan17/cybermem/commit/7871ba96c9008a8188a84bc379e9687e716ed9e9) Thanks [@mikhailkogan17-antigravity](https://github.com/mikhailkogan17-antigravity)! - fix(mcp): switch to SSEServerTransport for multi-client support
|
|
14
|
+
fix(dashboard): update mcp-config API to support SSE and --allow-http
|
|
15
|
+
|
|
16
|
+
## 0.14.6
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Automated release for patch version bump.
|
|
21
|
+
|
|
3
22
|
## 0.14.5
|
|
4
23
|
|
|
5
24
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
require("./console-fix.js");
|
|
7
7
|
require("./env.js");
|
|
8
8
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
9
|
+
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
9
10
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
10
|
-
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
11
11
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
12
12
|
const async_hooks_1 = require("async_hooks");
|
|
13
13
|
const cors_1 = __importDefault(require("cors"));
|
|
@@ -29,45 +29,6 @@ async function startServer() {
|
|
|
29
29
|
console.error("[MCP] Running in Staging environment");
|
|
30
30
|
process.env.CYBERMEM_ENV = "staging";
|
|
31
31
|
}
|
|
32
|
-
let stdioClientName = undefined;
|
|
33
|
-
// Protocol Instructions
|
|
34
|
-
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
35
|
-
PROTOCOL:
|
|
36
|
-
1. On session start: call query_memory("user context profile")
|
|
37
|
-
2. Store new insights immediately with add_memory (STABLE data)
|
|
38
|
-
3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
|
|
39
|
-
4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
|
|
40
|
-
5. Always include tags: [topic, year, source:your-client-name]
|
|
41
|
-
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
42
|
-
const server = new mcp_js_1.McpServer({ name: "cybermem", version: "0.12.4" }, {
|
|
43
|
-
instructions: CYBERMEM_INSTRUCTIONS,
|
|
44
|
-
});
|
|
45
|
-
server.registerResource("CyberMem Agent Protocol", "cybermem://protocol", { description: "Instructions for AI agents", mimeType: "text/plain" }, async () => ({
|
|
46
|
-
contents: [
|
|
47
|
-
{
|
|
48
|
-
uri: "cybermem://protocol",
|
|
49
|
-
mimeType: "text/plain",
|
|
50
|
-
text: CYBERMEM_INSTRUCTIONS,
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
}));
|
|
54
|
-
// Capture client info from handshake
|
|
55
|
-
// @ts-ignore - access underlying server
|
|
56
|
-
server.server.setRequestHandler(types_js_1.InitializeRequestSchema, async (request) => {
|
|
57
|
-
stdioClientName = request.params.clientInfo.name;
|
|
58
|
-
console.error(`[MCP] Client identified via handshake: ${stdioClientName}`);
|
|
59
|
-
return {
|
|
60
|
-
protocolVersion: "2024-11-05",
|
|
61
|
-
capabilities: {
|
|
62
|
-
tools: { listChanged: true },
|
|
63
|
-
resources: { subscribe: true },
|
|
64
|
-
},
|
|
65
|
-
serverInfo: {
|
|
66
|
-
name: "cybermem",
|
|
67
|
-
version: "0.12.4",
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
});
|
|
71
32
|
// --- IMPLEMENTATION LOGIC ---
|
|
72
33
|
let memory = null;
|
|
73
34
|
let sdk_update_memory = null;
|
|
@@ -88,7 +49,6 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
88
49
|
sdk_update_memory = hsg.update_memory;
|
|
89
50
|
sdk_reinforce_memory = hsg.reinforce_memory;
|
|
90
51
|
memory = new Memory();
|
|
91
|
-
server._memoryReady = true;
|
|
92
52
|
// Initialize Tables
|
|
93
53
|
const sqlite3 = await import("sqlite3");
|
|
94
54
|
const db = new sqlite3.default.Database(dbPath);
|
|
@@ -122,17 +82,32 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
122
82
|
});
|
|
123
83
|
});
|
|
124
84
|
};
|
|
85
|
+
let stdioClientName = undefined;
|
|
86
|
+
// Protocol Instructions
|
|
87
|
+
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
88
|
+
PROTOCOL:
|
|
89
|
+
1. On session start: call query_memory("user context profile")
|
|
90
|
+
2. Store new insights immediately with add_memory (STABLE data)
|
|
91
|
+
3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
|
|
92
|
+
4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
|
|
93
|
+
5. Always include tags: [topic, year, source:your-client-name]
|
|
94
|
+
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
125
95
|
const logActivity = async (operation, opts = {}) => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const { client: providedClient, method = "POST", endpoint = "/mcp", status = 200, } = opts;
|
|
96
|
+
// Determine client name (priority: specific > store > default)
|
|
97
|
+
let client;
|
|
129
98
|
const ctx = requestContext.getStore();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
99
|
+
if (opts.sessionId) {
|
|
100
|
+
client = "sse-client"; // TODO: Extract real client name from session state
|
|
101
|
+
}
|
|
102
|
+
else if (ctx) {
|
|
103
|
+
client = ctx.clientName || stdioClientName || "unknown";
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
client = stdioClientName || "unknown";
|
|
107
|
+
}
|
|
108
|
+
const { method = "POST", endpoint = "/mcp", status = 200 } = opts;
|
|
134
109
|
try {
|
|
135
|
-
const db =
|
|
110
|
+
const db = await initLoggingDb();
|
|
136
111
|
const ts = Date.now();
|
|
137
112
|
const is_error = status >= 400 ? 1 : 0;
|
|
138
113
|
db.serialize(() => {
|
|
@@ -151,92 +126,136 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
151
126
|
}
|
|
152
127
|
catch { }
|
|
153
128
|
};
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
CYBERMEM_INSTRUCTIONS,
|
|
158
|
-
inputSchema: zod_1.z.object({
|
|
159
|
-
content: zod_1.z.string(),
|
|
160
|
-
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
161
|
-
}),
|
|
162
|
-
}, async (args) => {
|
|
163
|
-
const res = await memory.add(args.content, { tags: args.tags });
|
|
164
|
-
await logActivity("create", {
|
|
165
|
-
method: "POST",
|
|
166
|
-
endpoint: "/memory/add",
|
|
167
|
-
status: 200,
|
|
129
|
+
// Factory to create configured McpServer instance
|
|
130
|
+
const createConfiguredServer = () => {
|
|
131
|
+
const server = new mcp_js_1.McpServer({ name: "cybermem", version: "0.12.4" }, {
|
|
132
|
+
instructions: CYBERMEM_INSTRUCTIONS,
|
|
168
133
|
});
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
description: "
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
134
|
+
// access underlying server
|
|
135
|
+
server._memoryReady =
|
|
136
|
+
true;
|
|
137
|
+
server.registerResource("CyberMem Agent Protocol", "cybermem://protocol", { description: "Instructions for AI agents", mimeType: "text/plain" }, async () => ({
|
|
138
|
+
contents: [
|
|
139
|
+
{
|
|
140
|
+
uri: "cybermem://protocol",
|
|
141
|
+
mimeType: "text/plain",
|
|
142
|
+
text: CYBERMEM_INSTRUCTIONS,
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
}));
|
|
146
|
+
// access underlying server
|
|
147
|
+
server.server.setRequestHandler(types_js_1.InitializeRequestSchema, async (request) => {
|
|
148
|
+
// For SSE multiple clients, stdioClientName global is less useful,
|
|
149
|
+
// but we can set it for context if running in single-user mode.
|
|
150
|
+
// For multi-user, rely on requestContext.
|
|
151
|
+
stdioClientName = request.params.clientInfo.name;
|
|
152
|
+
console.error(`[MCP] Client identified via handshake: ${request.params.clientInfo.name}`);
|
|
153
|
+
return {
|
|
154
|
+
protocolVersion: "2024-11-05",
|
|
155
|
+
capabilities: {
|
|
156
|
+
tools: { listChanged: true },
|
|
157
|
+
resources: { subscribe: true },
|
|
158
|
+
},
|
|
159
|
+
serverInfo: {
|
|
160
|
+
name: "cybermem",
|
|
161
|
+
version: "0.12.4",
|
|
162
|
+
},
|
|
163
|
+
};
|
|
180
164
|
});
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
status: 200,
|
|
165
|
+
// TOOLS
|
|
166
|
+
server.registerTool("add_memory", {
|
|
167
|
+
description: "Store a new memory. Use for high-quality, stable data. " +
|
|
168
|
+
CYBERMEM_INSTRUCTIONS,
|
|
169
|
+
inputSchema: zod_1.z.object({
|
|
170
|
+
content: zod_1.z.string(),
|
|
171
|
+
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
172
|
+
}),
|
|
173
|
+
}, async (args) => {
|
|
174
|
+
const res = await memory.add(args.content, { tags: args.tags });
|
|
175
|
+
await logActivity("create", {
|
|
176
|
+
method: "POST",
|
|
177
|
+
endpoint: "/memory/add",
|
|
178
|
+
status: 200,
|
|
179
|
+
});
|
|
180
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
198
181
|
});
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
endpoint: `/memory/${args.id}/reinforce`,
|
|
211
|
-
status: 200,
|
|
182
|
+
server.registerTool("query_memory", {
|
|
183
|
+
description: "Search memories.",
|
|
184
|
+
inputSchema: zod_1.z.object({ query: zod_1.z.string(), k: zod_1.z.number().default(50) }),
|
|
185
|
+
}, async (args) => {
|
|
186
|
+
const res = await memory.search(args.query, { limit: args.k });
|
|
187
|
+
await logActivity("read", {
|
|
188
|
+
method: "POST",
|
|
189
|
+
endpoint: "/memory/query",
|
|
190
|
+
status: 200,
|
|
191
|
+
});
|
|
192
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
212
193
|
});
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
194
|
+
server.registerTool("update_memory", {
|
|
195
|
+
description: "Mutate existing memory (content/tags). HIGH COST: re-embeds and re-links. Use for corrections.",
|
|
196
|
+
inputSchema: zod_1.z.object({
|
|
197
|
+
id: zod_1.z.string(),
|
|
198
|
+
content: zod_1.z.string().optional(),
|
|
199
|
+
tags: zod_1.z.array(zod_1.z.string()).optional(),
|
|
200
|
+
}),
|
|
201
|
+
}, async (args) => {
|
|
202
|
+
if (!sdk_update_memory)
|
|
203
|
+
throw new Error("Update not available in SDK");
|
|
204
|
+
if (args.content === undefined && args.tags === undefined) {
|
|
205
|
+
throw new Error("At least one of 'content' or 'tags' must be provided to update_memory");
|
|
206
|
+
}
|
|
207
|
+
const res = await sdk_update_memory(args.id, args.content, args.tags);
|
|
208
|
+
await logActivity("update", {
|
|
209
|
+
method: "PATCH",
|
|
210
|
+
endpoint: `/memory/${args.id}`,
|
|
211
|
+
status: 200,
|
|
212
|
+
});
|
|
213
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
214
|
+
});
|
|
215
|
+
server.registerTool("reinforce_memory", {
|
|
216
|
+
description: "Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
|
|
217
|
+
inputSchema: zod_1.z.object({
|
|
218
|
+
id: zod_1.z.string(),
|
|
219
|
+
boost: zod_1.z.number().default(0.1),
|
|
220
|
+
}),
|
|
221
|
+
}, async (args) => {
|
|
222
|
+
if (!sdk_reinforce_memory)
|
|
223
|
+
throw new Error("Reinforce not available in SDK");
|
|
224
|
+
const res = await sdk_reinforce_memory(args.id, args.boost);
|
|
225
|
+
await logActivity("update", {
|
|
226
|
+
method: "POST",
|
|
227
|
+
endpoint: `/memory/${args.id}/reinforce`,
|
|
228
|
+
status: 200,
|
|
229
|
+
});
|
|
230
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
231
|
+
});
|
|
232
|
+
server.registerTool("delete_memory", {
|
|
233
|
+
description: "Delete memory",
|
|
234
|
+
inputSchema: zod_1.z.object({ id: zod_1.z.string() }),
|
|
235
|
+
}, async (args) => {
|
|
236
|
+
const dbPath = process.env.OM_DB_PATH;
|
|
237
|
+
const sqlite3 = await import("sqlite3");
|
|
238
|
+
const db = new sqlite3.default.Database(dbPath);
|
|
239
|
+
return new Promise((resolve, reject) => {
|
|
240
|
+
db.serialize(() => {
|
|
241
|
+
db.run("DELETE FROM memories WHERE id = ?", [args.id]);
|
|
242
|
+
db.run("DELETE FROM vectors WHERE id = ?", [args.id], async (err) => {
|
|
243
|
+
db.close();
|
|
244
|
+
await logActivity("delete", {
|
|
245
|
+
method: "DELETE",
|
|
246
|
+
endpoint: `/memory/${args.id}`,
|
|
247
|
+
status: err ? 500 : 200,
|
|
248
|
+
});
|
|
249
|
+
if (err)
|
|
250
|
+
reject(err);
|
|
251
|
+
else
|
|
252
|
+
resolve({ content: [{ type: "text", text: "Deleted" }] });
|
|
231
253
|
});
|
|
232
|
-
if (err)
|
|
233
|
-
reject(err);
|
|
234
|
-
else
|
|
235
|
-
resolve({ content: [{ type: "text", text: "Deleted" }] });
|
|
236
254
|
});
|
|
237
255
|
});
|
|
238
256
|
});
|
|
239
|
-
|
|
257
|
+
return server;
|
|
258
|
+
};
|
|
240
259
|
// EXPRESS SERVER
|
|
241
260
|
// HTTP server mode for Docker/Traefik deployment
|
|
242
261
|
const useHttp = args.includes("--http") || args.includes("--port");
|
|
@@ -249,7 +268,6 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
249
268
|
app.use((req, res, next) => {
|
|
250
269
|
const clientName = req.headers["x-client-name"] || "antigravity-client";
|
|
251
270
|
requestContext.run({ clientName }, next);
|
|
252
|
-
// next(); // DELETED! Correctly handled by requestContext.run
|
|
253
271
|
});
|
|
254
272
|
if (memory) {
|
|
255
273
|
app.post("/add", async (req, res) => {
|
|
@@ -342,17 +360,63 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
342
360
|
});
|
|
343
361
|
});
|
|
344
362
|
}
|
|
345
|
-
|
|
346
|
-
|
|
363
|
+
// MULTI-SESSION SSE SUPPORT
|
|
364
|
+
const sessions = new Map();
|
|
365
|
+
// Legacy MCP endpoint - 410 Gone
|
|
366
|
+
app.all("/mcp", (req, res) => {
|
|
367
|
+
res
|
|
368
|
+
.status(410)
|
|
369
|
+
.send("Endpoint /mcp is deprecated. Please update your client configuration to use /sse for Server-Sent Events.");
|
|
347
370
|
});
|
|
348
|
-
app.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
371
|
+
app.get("/sse", async (req, res) => {
|
|
372
|
+
console.error("[MCP] Attempting SSE Connection...");
|
|
373
|
+
const transport = new sse_js_1.SSEServerTransport("/message", res);
|
|
374
|
+
const newServer = createConfiguredServer();
|
|
375
|
+
try {
|
|
376
|
+
await newServer.connect(transport);
|
|
377
|
+
sessions.set(transport.sessionId, { server: newServer, transport });
|
|
378
|
+
transport.onclose = () => {
|
|
379
|
+
console.error(`[MCP] SSE Connection Closed: ${transport.sessionId}`);
|
|
380
|
+
sessions.delete(transport.sessionId);
|
|
381
|
+
};
|
|
382
|
+
transport.onerror = (err) => {
|
|
383
|
+
console.error(`[MCP] SSE Connection Error: ${transport.sessionId}`, err);
|
|
384
|
+
sessions.delete(transport.sessionId);
|
|
385
|
+
};
|
|
386
|
+
await transport.start();
|
|
387
|
+
}
|
|
388
|
+
catch (err) {
|
|
389
|
+
console.error("[MCP] Failed to start SSE transport:", err);
|
|
390
|
+
sessions.delete(transport.sessionId);
|
|
391
|
+
// If headers haven't been sent, send 500
|
|
392
|
+
if (!res.headersSent) {
|
|
393
|
+
res.status(500).send("Internal Server Error during SSE handshake");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
app.post("/message", async (req, res) => {
|
|
398
|
+
const sessionId = req.query.sessionId;
|
|
399
|
+
const session = sessions.get(sessionId);
|
|
400
|
+
if (!session) {
|
|
401
|
+
res.status(404).send("Session not found");
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
try {
|
|
405
|
+
await session.transport.handlePostMessage(req, res);
|
|
406
|
+
}
|
|
407
|
+
catch (err) {
|
|
408
|
+
console.error(`[MCP] Error handling message for session ${sessionId}:`, err);
|
|
409
|
+
if (!res.headersSent) {
|
|
410
|
+
res.status(500).send("Internal Server Error processing message");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
352
413
|
});
|
|
414
|
+
app.listen(port, () => console.error(`CyberMem MCP running on http://localhost:${port}`));
|
|
353
415
|
}
|
|
354
416
|
else {
|
|
417
|
+
// STDIO
|
|
355
418
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
419
|
+
const server = createConfiguredServer();
|
|
356
420
|
server
|
|
357
421
|
.connect(transport)
|
|
358
422
|
.then(() => console.error("CyberMem MCP connected via STDIO"));
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2,14 +2,23 @@ import "./console-fix.js";
|
|
|
2
2
|
import "./env.js";
|
|
3
3
|
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
7
7
|
import { InitializeRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
8
|
import { AsyncLocalStorage } from "async_hooks";
|
|
9
9
|
import cors from "cors";
|
|
10
10
|
import express from "express";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
|
|
13
|
+
// Type definition for OpenMemory Memory class
|
|
14
|
+
interface IMemory {
|
|
15
|
+
add(content: string, opts?: { tags?: string[]; user_id?: string; [key: string]: unknown }): Promise<unknown>;
|
|
16
|
+
search(query: string, opts?: { limit?: number; user_id?: string; sectors?: unknown; [key: string]: unknown }): Promise<unknown>;
|
|
17
|
+
get(id: string): Promise<unknown>;
|
|
18
|
+
delete_all(user_id: string): Promise<unknown>;
|
|
19
|
+
wipe(): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
// Async Storage for Request Context (User ID and Client Name)
|
|
14
23
|
const requestContext = new AsyncLocalStorage<{
|
|
15
24
|
userId?: string;
|
|
@@ -35,63 +44,11 @@ async function startServer() {
|
|
|
35
44
|
process.env.CYBERMEM_ENV = "staging";
|
|
36
45
|
}
|
|
37
46
|
|
|
38
|
-
let stdioClientName: string | undefined = undefined;
|
|
39
|
-
|
|
40
|
-
// Protocol Instructions
|
|
41
|
-
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
42
|
-
PROTOCOL:
|
|
43
|
-
1. On session start: call query_memory("user context profile")
|
|
44
|
-
2. Store new insights immediately with add_memory (STABLE data)
|
|
45
|
-
3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
|
|
46
|
-
4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
|
|
47
|
-
5. Always include tags: [topic, year, source:your-client-name]
|
|
48
|
-
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
49
|
-
|
|
50
|
-
const server = new McpServer(
|
|
51
|
-
{ name: "cybermem", version: "0.12.4" },
|
|
52
|
-
{
|
|
53
|
-
instructions: CYBERMEM_INSTRUCTIONS,
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
server.registerResource(
|
|
58
|
-
"CyberMem Agent Protocol",
|
|
59
|
-
"cybermem://protocol",
|
|
60
|
-
{ description: "Instructions for AI agents", mimeType: "text/plain" },
|
|
61
|
-
async () => ({
|
|
62
|
-
contents: [
|
|
63
|
-
{
|
|
64
|
-
uri: "cybermem://protocol",
|
|
65
|
-
mimeType: "text/plain",
|
|
66
|
-
text: CYBERMEM_INSTRUCTIONS,
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
}),
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// Capture client info from handshake
|
|
73
|
-
// @ts-ignore - access underlying server
|
|
74
|
-
server.server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
75
|
-
stdioClientName = request.params.clientInfo.name;
|
|
76
|
-
console.error(`[MCP] Client identified via handshake: ${stdioClientName}`);
|
|
77
|
-
return {
|
|
78
|
-
protocolVersion: "2024-11-05",
|
|
79
|
-
capabilities: {
|
|
80
|
-
tools: { listChanged: true },
|
|
81
|
-
resources: { subscribe: true },
|
|
82
|
-
},
|
|
83
|
-
serverInfo: {
|
|
84
|
-
name: "cybermem",
|
|
85
|
-
version: "0.12.4",
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
|
|
90
47
|
// --- IMPLEMENTATION LOGIC ---
|
|
91
48
|
|
|
92
|
-
let memory:
|
|
93
|
-
let sdk_update_memory:
|
|
94
|
-
let sdk_reinforce_memory:
|
|
49
|
+
let memory: IMemory | null = null;
|
|
50
|
+
let sdk_update_memory: ((id: string, content?: string, tags?: string[], metadata?: Record<string, unknown>) => Promise<unknown>) | null = null;
|
|
51
|
+
let sdk_reinforce_memory: ((id: string, boost?: number) => Promise<unknown>) | null = null;
|
|
95
52
|
|
|
96
53
|
// LOCAL SDK MODE
|
|
97
54
|
const dbPath = process.env.OM_DB_PATH!;
|
|
@@ -107,8 +64,7 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
107
64
|
const hsg = await import("openmemory-js/dist/memory/hsg.js");
|
|
108
65
|
sdk_update_memory = hsg.update_memory;
|
|
109
66
|
sdk_reinforce_memory = hsg.reinforce_memory;
|
|
110
|
-
memory = new Memory();
|
|
111
|
-
(server as any)._memoryReady = true;
|
|
67
|
+
memory = new Memory() as IMemory;
|
|
112
68
|
|
|
113
69
|
// Initialize Tables
|
|
114
70
|
const sqlite3 = await import("sqlite3");
|
|
@@ -151,30 +107,47 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
151
107
|
});
|
|
152
108
|
};
|
|
153
109
|
|
|
110
|
+
let stdioClientName: string | undefined = undefined;
|
|
111
|
+
|
|
112
|
+
// Protocol Instructions
|
|
113
|
+
const CYBERMEM_INSTRUCTIONS = `CyberMem is a persistent context daemon for AI agents.
|
|
114
|
+
PROTOCOL:
|
|
115
|
+
1. On session start: call query_memory("user context profile")
|
|
116
|
+
2. Store new insights immediately with add_memory (STABLE data)
|
|
117
|
+
3. For corrections: use update_memory (STRUCTURAL mutation, high cost)
|
|
118
|
+
4. To prevent decay: use reinforce_memory (METABOLIC boost, low cost)
|
|
119
|
+
5. Always include tags: [topic, year, source:your-client-name]
|
|
120
|
+
For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
121
|
+
|
|
154
122
|
const logActivity = async (
|
|
155
123
|
operation: string,
|
|
156
124
|
opts: {
|
|
157
|
-
|
|
125
|
+
details?: unknown;
|
|
126
|
+
query?: string;
|
|
127
|
+
memoryId?: string;
|
|
128
|
+
delta?: string;
|
|
129
|
+
tags?: string[];
|
|
130
|
+
sessionId?: string;
|
|
158
131
|
method?: string;
|
|
159
132
|
endpoint?: string;
|
|
160
133
|
status?: number;
|
|
161
134
|
} = {},
|
|
162
135
|
) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
client: providedClient,
|
|
166
|
-
method = "POST",
|
|
167
|
-
endpoint = "/mcp",
|
|
168
|
-
status = 200,
|
|
169
|
-
} = opts;
|
|
136
|
+
// Determine client name (priority: specific > store > default)
|
|
137
|
+
let client: string;
|
|
170
138
|
const ctx = requestContext.getStore();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
139
|
+
|
|
140
|
+
if (opts.sessionId) {
|
|
141
|
+
client = "sse-client"; // TODO: Extract real client name from session state
|
|
142
|
+
} else if (ctx) {
|
|
143
|
+
client = ctx.clientName || stdioClientName || "unknown";
|
|
144
|
+
} else {
|
|
145
|
+
client = stdioClientName || "unknown";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { method = "POST", endpoint = "/mcp", status = 200 } = opts;
|
|
176
149
|
try {
|
|
177
|
-
const db =
|
|
150
|
+
const db = await initLoggingDb();
|
|
178
151
|
const ts = Date.now();
|
|
179
152
|
const is_error = status >= 400 ? 1 : 0;
|
|
180
153
|
db.serialize(() => {
|
|
@@ -199,120 +172,182 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
199
172
|
} catch {}
|
|
200
173
|
};
|
|
201
174
|
|
|
202
|
-
//
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
{
|
|
228
|
-
description: "Search memories.",
|
|
229
|
-
inputSchema: z.object({ query: z.string(), k: z.number().default(5) }),
|
|
230
|
-
},
|
|
231
|
-
async (args: any) => {
|
|
232
|
-
const res = await memory!.search(args.query, { limit: args.k });
|
|
233
|
-
await logActivity("read", {
|
|
234
|
-
method: "POST",
|
|
235
|
-
endpoint: "/memory/query",
|
|
236
|
-
status: 200,
|
|
237
|
-
});
|
|
238
|
-
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
239
|
-
},
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
server.registerTool(
|
|
243
|
-
"update_memory",
|
|
244
|
-
{
|
|
245
|
-
description:
|
|
246
|
-
"Mutate existing memory (content/tags). HIGH COST: re-embeds and re-links. Use for corrections.",
|
|
247
|
-
inputSchema: z.object({
|
|
248
|
-
id: z.string(),
|
|
249
|
-
content: z.string().optional(),
|
|
250
|
-
tags: z.array(z.string()).optional(),
|
|
175
|
+
// Factory to create configured McpServer instance
|
|
176
|
+
const createConfiguredServer = () => {
|
|
177
|
+
const server = new McpServer(
|
|
178
|
+
{ name: "cybermem", version: "0.12.4" },
|
|
179
|
+
{
|
|
180
|
+
instructions: CYBERMEM_INSTRUCTIONS,
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// access underlying server
|
|
185
|
+
(server as unknown as McpServer & { _memoryReady: boolean })._memoryReady =
|
|
186
|
+
true;
|
|
187
|
+
|
|
188
|
+
server.registerResource(
|
|
189
|
+
"CyberMem Agent Protocol",
|
|
190
|
+
"cybermem://protocol",
|
|
191
|
+
{ description: "Instructions for AI agents", mimeType: "text/plain" },
|
|
192
|
+
async () => ({
|
|
193
|
+
contents: [
|
|
194
|
+
{
|
|
195
|
+
uri: "cybermem://protocol",
|
|
196
|
+
mimeType: "text/plain",
|
|
197
|
+
text: CYBERMEM_INSTRUCTIONS,
|
|
198
|
+
},
|
|
199
|
+
],
|
|
251
200
|
}),
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
db.run("DELETE FROM memories WHERE id = ?", [args.id]);
|
|
298
|
-
db.run(
|
|
299
|
-
"DELETE FROM vectors WHERE id = ?",
|
|
300
|
-
[args.id],
|
|
301
|
-
async (err: any) => {
|
|
302
|
-
db.close();
|
|
303
|
-
await logActivity("delete", {
|
|
304
|
-
method: "DELETE",
|
|
305
|
-
endpoint: `/memory/${args.id}`,
|
|
306
|
-
status: err ? 500 : 200,
|
|
307
|
-
});
|
|
308
|
-
if (err) reject(err);
|
|
309
|
-
else resolve({ content: [{ type: "text", text: "Deleted" }] });
|
|
310
|
-
},
|
|
311
|
-
);
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
// access underlying server
|
|
204
|
+
server.server.setRequestHandler(
|
|
205
|
+
InitializeRequestSchema,
|
|
206
|
+
async (request) => {
|
|
207
|
+
// For SSE multiple clients, stdioClientName global is less useful,
|
|
208
|
+
// but we can set it for context if running in single-user mode.
|
|
209
|
+
// For multi-user, rely on requestContext.
|
|
210
|
+
stdioClientName = request.params.clientInfo.name;
|
|
211
|
+
console.error(
|
|
212
|
+
`[MCP] Client identified via handshake: ${request.params.clientInfo.name}`,
|
|
213
|
+
);
|
|
214
|
+
return {
|
|
215
|
+
protocolVersion: "2024-11-05",
|
|
216
|
+
capabilities: {
|
|
217
|
+
tools: { listChanged: true },
|
|
218
|
+
resources: { subscribe: true },
|
|
219
|
+
},
|
|
220
|
+
serverInfo: {
|
|
221
|
+
name: "cybermem",
|
|
222
|
+
version: "0.12.4",
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// TOOLS
|
|
229
|
+
server.registerTool(
|
|
230
|
+
"add_memory",
|
|
231
|
+
{
|
|
232
|
+
description:
|
|
233
|
+
"Store a new memory. Use for high-quality, stable data. " +
|
|
234
|
+
CYBERMEM_INSTRUCTIONS,
|
|
235
|
+
inputSchema: z.object({
|
|
236
|
+
content: z.string(),
|
|
237
|
+
tags: z.array(z.string()).optional(),
|
|
238
|
+
}),
|
|
239
|
+
},
|
|
240
|
+
async (args: { content: string; tags?: string[] }) => {
|
|
241
|
+
const res = await memory!.add(args.content, { tags: args.tags });
|
|
242
|
+
await logActivity("create", {
|
|
243
|
+
method: "POST",
|
|
244
|
+
endpoint: "/memory/add",
|
|
245
|
+
status: 200,
|
|
312
246
|
});
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
247
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
server.registerTool(
|
|
252
|
+
"query_memory",
|
|
253
|
+
{
|
|
254
|
+
description: "Search memories.",
|
|
255
|
+
inputSchema: z.object({ query: z.string(), k: z.number().default(50) }),
|
|
256
|
+
},
|
|
257
|
+
async (args: { query: string; k?: number }) => {
|
|
258
|
+
const res = await memory!.search(args.query, { limit: args.k });
|
|
259
|
+
await logActivity("read", {
|
|
260
|
+
method: "POST",
|
|
261
|
+
endpoint: "/memory/query",
|
|
262
|
+
status: 200,
|
|
263
|
+
});
|
|
264
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
265
|
+
},
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
server.registerTool(
|
|
269
|
+
"update_memory",
|
|
270
|
+
{
|
|
271
|
+
description:
|
|
272
|
+
"Mutate existing memory (content/tags). HIGH COST: re-embeds and re-links. Use for corrections.",
|
|
273
|
+
inputSchema: z.object({
|
|
274
|
+
id: z.string(),
|
|
275
|
+
content: z.string().optional(),
|
|
276
|
+
tags: z.array(z.string()).optional(),
|
|
277
|
+
}),
|
|
278
|
+
},
|
|
279
|
+
async (args: { id: string; content?: string; tags?: string[] }) => {
|
|
280
|
+
if (!sdk_update_memory) throw new Error("Update not available in SDK");
|
|
281
|
+
if (args.content === undefined && args.tags === undefined) {
|
|
282
|
+
throw new Error("At least one of 'content' or 'tags' must be provided to update_memory");
|
|
283
|
+
}
|
|
284
|
+
const res = await sdk_update_memory(args.id, args.content, args.tags);
|
|
285
|
+
await logActivity("update", {
|
|
286
|
+
method: "PATCH",
|
|
287
|
+
endpoint: `/memory/${args.id}`,
|
|
288
|
+
status: 200,
|
|
289
|
+
});
|
|
290
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
291
|
+
},
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
server.registerTool(
|
|
295
|
+
"reinforce_memory",
|
|
296
|
+
{
|
|
297
|
+
description:
|
|
298
|
+
"Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
|
|
299
|
+
inputSchema: z.object({
|
|
300
|
+
id: z.string(),
|
|
301
|
+
boost: z.number().default(0.1),
|
|
302
|
+
}),
|
|
303
|
+
},
|
|
304
|
+
async (args: { id: string; boost?: number }) => {
|
|
305
|
+
if (!sdk_reinforce_memory)
|
|
306
|
+
throw new Error("Reinforce not available in SDK");
|
|
307
|
+
const res = await sdk_reinforce_memory(args.id, args.boost);
|
|
308
|
+
await logActivity("update", {
|
|
309
|
+
method: "POST",
|
|
310
|
+
endpoint: `/memory/${args.id}/reinforce`,
|
|
311
|
+
status: 200,
|
|
312
|
+
});
|
|
313
|
+
return { content: [{ type: "text", text: JSON.stringify(res) }] };
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
server.registerTool(
|
|
318
|
+
"delete_memory",
|
|
319
|
+
{
|
|
320
|
+
description: "Delete memory",
|
|
321
|
+
inputSchema: z.object({ id: z.string() }),
|
|
322
|
+
},
|
|
323
|
+
async (args: { id: string }) => {
|
|
324
|
+
const dbPath = process.env.OM_DB_PATH!;
|
|
325
|
+
const sqlite3 = await import("sqlite3");
|
|
326
|
+
const db = new sqlite3.default.Database(dbPath);
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
db.serialize(() => {
|
|
329
|
+
db.run("DELETE FROM memories WHERE id = ?", [args.id]);
|
|
330
|
+
db.run(
|
|
331
|
+
"DELETE FROM vectors WHERE id = ?",
|
|
332
|
+
[args.id],
|
|
333
|
+
async (err: Error | null) => {
|
|
334
|
+
db.close();
|
|
335
|
+
await logActivity("delete", {
|
|
336
|
+
method: "DELETE",
|
|
337
|
+
endpoint: `/memory/${args.id}`,
|
|
338
|
+
status: err ? 500 : 200,
|
|
339
|
+
});
|
|
340
|
+
if (err) reject(err);
|
|
341
|
+
else resolve({ content: [{ type: "text", text: "Deleted" }] });
|
|
342
|
+
},
|
|
343
|
+
);
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
},
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
return server;
|
|
350
|
+
};
|
|
316
351
|
|
|
317
352
|
// EXPRESS SERVER
|
|
318
353
|
// HTTP server mode for Docker/Traefik deployment
|
|
@@ -328,7 +363,6 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
328
363
|
const clientName =
|
|
329
364
|
(req.headers["x-client-name"] as string) || "antigravity-client";
|
|
330
365
|
requestContext.run({ clientName }, next);
|
|
331
|
-
// next(); // DELETED! Correctly handled by requestContext.run
|
|
332
366
|
});
|
|
333
367
|
|
|
334
368
|
if (memory) {
|
|
@@ -427,25 +461,83 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
|
|
|
427
461
|
});
|
|
428
462
|
}
|
|
429
463
|
|
|
430
|
-
|
|
431
|
-
|
|
464
|
+
// MULTI-SESSION SSE SUPPORT
|
|
465
|
+
const sessions = new Map<
|
|
466
|
+
string,
|
|
467
|
+
{
|
|
468
|
+
server: McpServer;
|
|
469
|
+
transport: SSEServerTransport;
|
|
470
|
+
}
|
|
471
|
+
>();
|
|
472
|
+
|
|
473
|
+
// Legacy MCP endpoint - 410 Gone
|
|
474
|
+
app.all("/mcp", (req, res) => {
|
|
475
|
+
res
|
|
476
|
+
.status(410)
|
|
477
|
+
.send(
|
|
478
|
+
"Endpoint /mcp is deprecated. Please update your client configuration to use /sse for Server-Sent Events.",
|
|
479
|
+
);
|
|
432
480
|
});
|
|
433
|
-
app.all(
|
|
434
|
-
"/mcp",
|
|
435
|
-
async (req, res) => await transport.handleRequest(req, res, req.body),
|
|
436
|
-
);
|
|
437
|
-
app.all(
|
|
438
|
-
"/sse",
|
|
439
|
-
async (req, res) => await transport.handleRequest(req, res, req.body),
|
|
440
|
-
);
|
|
441
481
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
);
|
|
482
|
+
app.get("/sse", async (req, res) => {
|
|
483
|
+
console.error("[MCP] Attempting SSE Connection...");
|
|
484
|
+
const transport = new SSEServerTransport("/message", res);
|
|
485
|
+
const newServer = createConfiguredServer();
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
await newServer.connect(transport);
|
|
489
|
+
sessions.set(transport.sessionId, { server: newServer, transport });
|
|
490
|
+
|
|
491
|
+
transport.onclose = () => {
|
|
492
|
+
console.error(`[MCP] SSE Connection Closed: ${transport.sessionId}`);
|
|
493
|
+
sessions.delete(transport.sessionId);
|
|
494
|
+
};
|
|
495
|
+
transport.onerror = (err: Error) => {
|
|
496
|
+
console.error(
|
|
497
|
+
`[MCP] SSE Connection Error: ${transport.sessionId}`,
|
|
498
|
+
err,
|
|
499
|
+
);
|
|
500
|
+
sessions.delete(transport.sessionId);
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
await transport.start();
|
|
504
|
+
} catch (err) {
|
|
505
|
+
console.error("[MCP] Failed to start SSE transport:", err);
|
|
506
|
+
sessions.delete(transport.sessionId);
|
|
507
|
+
// If headers haven't been sent, send 500
|
|
508
|
+
if (!res.headersSent) {
|
|
509
|
+
res.status(500).send("Internal Server Error during SSE handshake");
|
|
510
|
+
}
|
|
511
|
+
}
|
|
446
512
|
});
|
|
513
|
+
|
|
514
|
+
app.post("/message", async (req, res) => {
|
|
515
|
+
const sessionId = req.query.sessionId as string;
|
|
516
|
+
const session = sessions.get(sessionId);
|
|
517
|
+
if (!session) {
|
|
518
|
+
res.status(404).send("Session not found");
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
await session.transport.handlePostMessage(req, res);
|
|
523
|
+
} catch (err) {
|
|
524
|
+
console.error(
|
|
525
|
+
`[MCP] Error handling message for session ${sessionId}:`,
|
|
526
|
+
err,
|
|
527
|
+
);
|
|
528
|
+
if (!res.headersSent) {
|
|
529
|
+
res.status(500).send("Internal Server Error processing message");
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
app.listen(port, () =>
|
|
535
|
+
console.error(`CyberMem MCP running on http://localhost:${port}`),
|
|
536
|
+
);
|
|
447
537
|
} else {
|
|
538
|
+
// STDIO
|
|
448
539
|
const transport = new StdioServerTransport();
|
|
540
|
+
const server = createConfiguredServer();
|
|
449
541
|
server
|
|
450
542
|
.connect(transport)
|
|
451
543
|
.then(() => console.error("CyberMem MCP connected via STDIO"));
|