@cybermem/mcp 0.14.6 → 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 CHANGED
@@ -1,5 +1,18 @@
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
+
3
16
  ## 0.14.6
4
17
 
5
18
  ### 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
- if (!memory)
127
- return;
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
- const client = providedClient ||
131
- ctx?.clientName ||
132
- stdioClientName ||
133
- "antigravity-client";
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 = (await initLoggingDb());
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
- // TOOLS
155
- server.registerTool("add_memory", {
156
- description: "Store a new memory. Use for high-quality, stable data. " +
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
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
170
- });
171
- server.registerTool("query_memory", {
172
- description: "Search memories.",
173
- inputSchema: zod_1.z.object({ query: zod_1.z.string(), k: zod_1.z.number().default(5) }),
174
- }, async (args) => {
175
- const res = await memory.search(args.query, { limit: args.k });
176
- await logActivity("read", {
177
- method: "POST",
178
- endpoint: "/memory/query",
179
- status: 200,
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
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
182
- });
183
- server.registerTool("update_memory", {
184
- description: "Mutate existing memory (content/tags). HIGH COST: re-embeds and re-links. Use for corrections.",
185
- inputSchema: zod_1.z.object({
186
- id: zod_1.z.string(),
187
- content: zod_1.z.string().optional(),
188
- tags: zod_1.z.array(zod_1.z.string()).optional(),
189
- }),
190
- }, async (args) => {
191
- if (!sdk_update_memory)
192
- throw new Error("Update not available in SDK");
193
- const res = await sdk_update_memory(args.id, args.content, args.tags);
194
- await logActivity("update", {
195
- method: "PATCH",
196
- endpoint: `/memory/${args.id}`,
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
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
200
- });
201
- server.registerTool("reinforce_memory", {
202
- description: "Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
203
- inputSchema: zod_1.z.object({ id: zod_1.z.string(), boost: zod_1.z.number().default(0.1) }),
204
- }, async (args) => {
205
- if (!sdk_reinforce_memory)
206
- throw new Error("Reinforce not available in SDK");
207
- await sdk_reinforce_memory(args.id, args.boost);
208
- await logActivity("update", {
209
- method: "POST",
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
- return { content: [{ type: "text", text: "Reinforced" }] };
214
- });
215
- server.registerTool("delete_memory", {
216
- description: "Delete memory",
217
- inputSchema: zod_1.z.object({ id: zod_1.z.string() }),
218
- }, async (args) => {
219
- const dbPath = process.env.OM_DB_PATH;
220
- const sqlite3 = await import("sqlite3");
221
- const db = new sqlite3.default.Database(dbPath);
222
- return new Promise((resolve, reject) => {
223
- db.serialize(() => {
224
- db.run("DELETE FROM memories WHERE id = ?", [args.id]);
225
- db.run("DELETE FROM vectors WHERE id = ?", [args.id], async (err) => {
226
- db.close();
227
- await logActivity("delete", {
228
- method: "DELETE",
229
- endpoint: `/memory/${args.id}`,
230
- status: err ? 500 : 200,
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
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
346
- sessionIdGenerator: () => crypto.randomUUID(),
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.all("/mcp", async (req, res) => await transport.handleRequest(req, res, req.body));
349
- app.all("/sse", async (req, res) => await transport.handleRequest(req, res, req.body));
350
- server.connect(transport).then(() => {
351
- app.listen(port, () => console.error(`CyberMem MCP running on http://localhost:${port}`));
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/mcp",
3
- "version": "0.14.6",
3
+ "version": "0.14.8",
4
4
  "description": "CyberMem MCP Server - AI Memory with openmemory-js SDK",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
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: any = null;
93
- let sdk_update_memory: any = null;
94
- let sdk_reinforce_memory: any = null;
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
- client?: string;
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
- if (!memory) return;
164
- const {
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
- const client =
172
- providedClient ||
173
- ctx?.clientName ||
174
- stdioClientName ||
175
- "antigravity-client";
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 = (await initLoggingDb()) as any;
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
- // TOOLS
203
- server.registerTool(
204
- "add_memory",
205
- {
206
- description:
207
- "Store a new memory. Use for high-quality, stable data. " +
208
- CYBERMEM_INSTRUCTIONS,
209
- inputSchema: z.object({
210
- content: z.string(),
211
- tags: z.array(z.string()).optional(),
212
- }),
213
- },
214
- async (args: any) => {
215
- const res = await memory!.add(args.content, { tags: args.tags });
216
- await logActivity("create", {
217
- method: "POST",
218
- endpoint: "/memory/add",
219
- status: 200,
220
- });
221
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
222
- },
223
- );
224
-
225
- server.registerTool(
226
- "query_memory",
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
- async (args: any) => {
254
- if (!sdk_update_memory) throw new Error("Update not available in SDK");
255
- const res = await sdk_update_memory(args.id, args.content, args.tags);
256
- await logActivity("update", {
257
- method: "PATCH",
258
- endpoint: `/memory/${args.id}`,
259
- status: 200,
260
- });
261
- return { content: [{ type: "text", text: JSON.stringify(res) }] };
262
- },
263
- );
264
-
265
- server.registerTool(
266
- "reinforce_memory",
267
- {
268
- description:
269
- "Metabolic boost (salience). LOW COST: prevents decay without mutation. Use for active topics.",
270
- inputSchema: z.object({ id: z.string(), boost: z.number().default(0.1) }),
271
- },
272
- async (args: any) => {
273
- if (!sdk_reinforce_memory)
274
- throw new Error("Reinforce not available in SDK");
275
- await sdk_reinforce_memory(args.id, args.boost);
276
- await logActivity("update", {
277
- method: "POST",
278
- endpoint: `/memory/${args.id}/reinforce`,
279
- status: 200,
280
- });
281
- return { content: [{ type: "text", text: "Reinforced" }] };
282
- },
283
- );
284
-
285
- server.registerTool(
286
- "delete_memory",
287
- {
288
- description: "Delete memory",
289
- inputSchema: z.object({ id: z.string() }),
290
- },
291
- async (args: any) => {
292
- const dbPath = process.env.OM_DB_PATH!;
293
- const sqlite3 = await import("sqlite3");
294
- const db = new sqlite3.default.Database(dbPath);
295
- return new Promise((resolve, reject) => {
296
- db.serialize(() => {
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
- const transport = new StreamableHTTPServerTransport({
431
- sessionIdGenerator: () => crypto.randomUUID(),
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
- server.connect(transport).then(() => {
443
- app.listen(port, () =>
444
- console.error(`CyberMem MCP running on http://localhost:${port}`),
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"));