@cybermem/mcp 0.15.0 → 0.16.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @cybermem/mcp
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#140](https://github.com/mikhailkogan17/cybermem/pull/140) [`bcf40e2`](https://github.com/mikhailkogan17/cybermem/commit/bcf40e2173ad66214cfc6089d45481966255581d) Thanks [@mikhailkogan17-antigravity](https://github.com/mikhailkogan17-antigravity)! - Moved server tools usage to FastMCP
8
+
3
9
  ## 0.15.0
4
10
 
5
11
  ### Minor Changes
package/dist/index.js CHANGED
@@ -89,12 +89,28 @@ async function initialize() {
89
89
  process.exit(1);
90
90
  }
91
91
  }
92
+ /**
93
+ * Derives the client name from the tool execution context.
94
+ * Falls back to handshake client name if session is 'stdio'.
95
+ */
96
+ function getClientName(context) {
97
+ const ctx = context;
98
+ const sessionName = ctx.session?.clientName;
99
+ if (sessionName === "stdio" && ctx.client?.version?.name) {
100
+ return ctx.client.version.name;
101
+ }
102
+ return sessionName || "unknown";
103
+ }
92
104
  const server = new fastmcp_1.FastMCP({
93
105
  name: "cybermem",
94
106
  version: VALID_VERSION,
95
107
  instructions: CYBERMEM_INSTRUCTIONS,
96
108
  health: { enabled: true, path: "/health" },
97
109
  authenticate: async (req) => {
110
+ // STDIO transport doesn't provide an HTTP request object
111
+ if (!req?.headers) {
112
+ return { clientName: "stdio" };
113
+ }
98
114
  const clientName = (req.headers["x-client-name"] ||
99
115
  req.headers["X-Client-Name"] ||
100
116
  "unknown");
@@ -120,7 +136,8 @@ server.addTool({
120
136
  tags: zod_1.z.array(zod_1.z.string()).optional().describe("Category tags"),
121
137
  }),
122
138
  execute: async (args, context) => {
123
- return requestContext.run({ clientName: context.session?.clientName }, async () => {
139
+ const clientName = getClientName(context);
140
+ return requestContext.run({ clientName }, async () => {
124
141
  try {
125
142
  const res = await memory.add(args.content, { tags: args.tags });
126
143
  await logActivity("add_memory");
@@ -141,7 +158,8 @@ server.addTool({
141
158
  k: zod_1.z.number().default(5).describe("Number of results"),
142
159
  }),
143
160
  execute: async (args, context) => {
144
- return requestContext.run({ clientName: context.session?.clientName }, async () => {
161
+ const clientName = getClientName(context);
162
+ return requestContext.run({ clientName }, async () => {
145
163
  try {
146
164
  const res = await memory.search(args.query, { limit: args.k });
147
165
  await logActivity("query_memory");
@@ -168,7 +186,8 @@ server.addTool({
168
186
  path: ["content"],
169
187
  }),
170
188
  execute: async (args, context) => {
171
- return requestContext.run({ clientName: context.session?.clientName }, async () => {
189
+ const clientName = getClientName(context);
190
+ return requestContext.run({ clientName }, async () => {
172
191
  try {
173
192
  const res = await (0, hsg_js_1.update_memory)(args.id, args.content, args.tags);
174
193
  await logActivity("update_memory");
@@ -192,7 +211,8 @@ server.addTool({
192
211
  .describe("Relevance boost amount (0.0 to 1.0)"),
193
212
  }),
194
213
  execute: async (args, context) => {
195
- return requestContext.run({ clientName: context.session?.clientName }, async () => {
214
+ const clientName = getClientName(context);
215
+ return requestContext.run({ clientName }, async () => {
196
216
  try {
197
217
  await (0, hsg_js_1.reinforce_memory)(args.id, args.boost);
198
218
  await logActivity("reinforce_memory");
@@ -212,7 +232,8 @@ server.addTool({
212
232
  id: zod_1.z.string().describe("Memory ID"),
213
233
  }),
214
234
  execute: async (args, context) => {
215
- return requestContext.run({ clientName: context.session?.clientName }, async () => {
235
+ const clientName = getClientName(context);
236
+ return requestContext.run({ clientName }, async () => {
216
237
  try {
217
238
  await (0, db_js_1.run_async)("DELETE FROM memories WHERE id=?", [args.id]);
218
239
  await (0, db_js_1.run_async)("DELETE FROM vectors WHERE id=?", [args.id]);
@@ -0,0 +1,74 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { spawn } from "child_process";
3
+ import path from "path";
4
+
5
+ const TEST_CLIENT_NAME = "antigravity-e2e-stdio";
6
+
7
+ test("MCP:E2E (STDIO Attribution) — Verified handshake identity propagates to logs", async () => {
8
+ const serverPath = path.join(__dirname, "../dist/index.js");
9
+
10
+ // 1. Spawn the server in STDIO mode
11
+ const server = spawn("node", [serverPath], {
12
+ env: {
13
+ ...process.env,
14
+ OM_DB_PATH: ":memory:",
15
+ CYBERMEM_INSTANCE: "stdio-e2e-test",
16
+ },
17
+ });
18
+
19
+ let logOutput = "";
20
+ // In STDIO mode, console.log is redirected to stderr by console-fix.ts
21
+ server.stderr?.on("data", (data) => {
22
+ logOutput += data.toString();
23
+ });
24
+
25
+ // 2. Send initialize handshake with custom clientInfo
26
+ server.stdin?.write(
27
+ JSON.stringify({
28
+ jsonrpc: "2.0",
29
+ id: 1,
30
+ method: "initialize",
31
+ params: {
32
+ protocolVersion: "2024-11-05",
33
+ capabilities: {},
34
+ clientInfo: {
35
+ name: TEST_CLIENT_NAME,
36
+ version: "1.0.0",
37
+ },
38
+ },
39
+ }) + "\n",
40
+ );
41
+
42
+ // Wait for initialization to be processed
43
+ await new Promise((r) => setTimeout(r, 500));
44
+
45
+ // 3. Send tool call
46
+ server.stdin?.write(
47
+ JSON.stringify({
48
+ jsonrpc: "2.0",
49
+ id: 2,
50
+ method: "tools/call",
51
+ params: {
52
+ name: "query_memory",
53
+ arguments: {
54
+ query: "test attribution",
55
+ },
56
+ },
57
+ }) + "\n",
58
+ );
59
+
60
+ // Wait for tool execution and logging (console.log is async in some buffers)
61
+ await new Promise((r) => setTimeout(r, 1000));
62
+
63
+ // Cleanup
64
+ server.kill();
65
+
66
+ // 4. Assert that the log output contains the correctly attributed client name
67
+ const expectedLogLine = `[MCP-LOG] client=${TEST_CLIENT_NAME} tool=query_memory`;
68
+
69
+ if (!logOutput.includes(expectedLogLine)) {
70
+ console.log("Full stderr received:", logOutput);
71
+ }
72
+
73
+ expect(logOutput).toContain(expectedLogLine);
74
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
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
@@ -12,8 +12,8 @@ import { z } from "zod";
12
12
  import { all_async, run_async } from "openmemory-js/dist/core/db.js";
13
13
  import { Memory } from "openmemory-js/dist/core/memory.js";
14
14
  import {
15
- reinforce_memory,
16
- update_memory,
15
+ reinforce_memory,
16
+ update_memory,
17
17
  } from "openmemory-js/dist/memory/hsg.js";
18
18
 
19
19
  // --- TYPES ---
@@ -148,12 +148,41 @@ interface AuthContext {
148
148
  [key: string]: unknown;
149
149
  }
150
150
 
151
+ /**
152
+ * Extended context for MCP tools to handle STDIO client attribution.
153
+ */
154
+ interface ToolContext {
155
+ session?: AuthContext;
156
+ client?: {
157
+ version: {
158
+ name: string;
159
+ };
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Derives the client name from the tool execution context.
165
+ * Falls back to handshake client name if session is 'stdio'.
166
+ */
167
+ function getClientName(context: any): string {
168
+ const ctx = context as ToolContext;
169
+ const sessionName = ctx.session?.clientName;
170
+ if (sessionName === "stdio" && ctx.client?.version?.name) {
171
+ return ctx.client.version.name;
172
+ }
173
+ return sessionName || "unknown";
174
+ }
175
+
151
176
  const server = new FastMCP<AuthContext>({
152
177
  name: "cybermem",
153
178
  version: VALID_VERSION,
154
179
  instructions: CYBERMEM_INSTRUCTIONS,
155
180
  health: { enabled: true, path: "/health" },
156
181
  authenticate: async (req) => {
182
+ // STDIO transport doesn't provide an HTTP request object
183
+ if (!req?.headers) {
184
+ return { clientName: "stdio" };
185
+ }
157
186
  const clientName = (req.headers["x-client-name"] ||
158
187
  req.headers["X-Client-Name"] ||
159
188
  "unknown") as string;
@@ -184,19 +213,18 @@ server.addTool({
184
213
  tags: z.array(z.string()).optional().describe("Category tags"),
185
214
  }),
186
215
  execute: async (args, context) => {
187
- return requestContext.run(
188
- { clientName: context.session?.clientName },
189
- async () => {
190
- try {
191
- const res = await memory.add(args.content, { tags: args.tags });
192
- await logActivity("add_memory");
193
- return JSON.stringify(res);
194
- } catch (err: any) {
195
- await logActivity("add_memory", 500);
196
- throw err;
197
- }
198
- },
199
- );
216
+ const clientName = getClientName(context);
217
+
218
+ return requestContext.run({ clientName }, async () => {
219
+ try {
220
+ const res = await memory.add(args.content, { tags: args.tags });
221
+ await logActivity("add_memory");
222
+ return JSON.stringify(res);
223
+ } catch (err: any) {
224
+ await logActivity("add_memory", 500);
225
+ throw err;
226
+ }
227
+ });
200
228
  },
201
229
  });
202
230
 
@@ -208,19 +236,18 @@ server.addTool({
208
236
  k: z.number().default(5).describe("Number of results"),
209
237
  }),
210
238
  execute: async (args, context) => {
211
- return requestContext.run(
212
- { clientName: context.session?.clientName },
213
- async () => {
214
- try {
215
- const res = await memory.search(args.query, { limit: args.k });
216
- await logActivity("query_memory");
217
- return JSON.stringify(res);
218
- } catch (err: any) {
219
- await logActivity("query_memory", 500);
220
- throw err;
221
- }
222
- },
223
- );
239
+ const clientName = getClientName(context);
240
+
241
+ return requestContext.run({ clientName }, async () => {
242
+ try {
243
+ const res = await memory.search(args.query, { limit: args.k });
244
+ await logActivity("query_memory");
245
+ return JSON.stringify(res);
246
+ } catch (err: any) {
247
+ await logActivity("query_memory", 500);
248
+ throw err;
249
+ }
250
+ });
224
251
  },
225
252
  });
226
253
 
@@ -239,19 +266,18 @@ server.addTool({
239
266
  path: ["content"],
240
267
  }),
241
268
  execute: async (args, context) => {
242
- return requestContext.run(
243
- { clientName: context.session?.clientName },
244
- async () => {
245
- try {
246
- const res = await update_memory(args.id, args.content, args.tags);
247
- await logActivity("update_memory");
248
- return JSON.stringify(res);
249
- } catch (err: any) {
250
- await logActivity("update_memory", 500);
251
- throw err;
252
- }
253
- },
254
- );
269
+ const clientName = getClientName(context);
270
+
271
+ return requestContext.run({ clientName }, async () => {
272
+ try {
273
+ const res = await update_memory(args.id, args.content, args.tags);
274
+ await logActivity("update_memory");
275
+ return JSON.stringify(res);
276
+ } catch (err: any) {
277
+ await logActivity("update_memory", 500);
278
+ throw err;
279
+ }
280
+ });
255
281
  },
256
282
  });
257
283
 
@@ -266,19 +292,18 @@ server.addTool({
266
292
  .describe("Relevance boost amount (0.0 to 1.0)"),
267
293
  }),
268
294
  execute: async (args, context) => {
269
- return requestContext.run(
270
- { clientName: context.session?.clientName },
271
- async () => {
272
- try {
273
- await reinforce_memory(args.id, args.boost);
274
- await logActivity("reinforce_memory");
275
- return `Memory reinforced: ${args.id}`;
276
- } catch (err: any) {
277
- await logActivity("reinforce_memory", 500);
278
- throw err;
279
- }
280
- },
281
- );
295
+ const clientName = getClientName(context);
296
+
297
+ return requestContext.run({ clientName }, async () => {
298
+ try {
299
+ await reinforce_memory(args.id, args.boost);
300
+ await logActivity("reinforce_memory");
301
+ return `Memory reinforced: ${args.id}`;
302
+ } catch (err: any) {
303
+ await logActivity("reinforce_memory", 500);
304
+ throw err;
305
+ }
306
+ });
282
307
  },
283
308
  });
284
309
 
@@ -289,20 +314,19 @@ server.addTool({
289
314
  id: z.string().describe("Memory ID"),
290
315
  }),
291
316
  execute: async (args, context) => {
292
- return requestContext.run(
293
- { clientName: context.session?.clientName },
294
- async () => {
295
- try {
296
- await run_async("DELETE FROM memories WHERE id=?", [args.id]);
297
- await run_async("DELETE FROM vectors WHERE id=?", [args.id]);
298
- await logActivity("delete_memory");
299
- return "Deleted";
300
- } catch (err: any) {
301
- await logActivity("delete_memory", 500);
302
- throw err;
303
- }
304
- },
305
- );
317
+ const clientName = getClientName(context);
318
+
319
+ return requestContext.run({ clientName }, async () => {
320
+ try {
321
+ await run_async("DELETE FROM memories WHERE id=?", [args.id]);
322
+ await run_async("DELETE FROM vectors WHERE id=?", [args.id]);
323
+ await logActivity("delete_memory");
324
+ return "Deleted";
325
+ } catch (err: any) {
326
+ await logActivity("delete_memory", 500);
327
+ throw err;
328
+ }
329
+ });
306
330
  },
307
331
  });
308
332