@cybermem/mcp 0.14.13 → 0.14.14

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.14.14
4
+
5
+ ### Patch Changes
6
+
7
+ - Automated patch version bump.
8
+
3
9
  ## 0.14.13
4
10
 
5
11
  ### Patch Changes
package/dist/index.js CHANGED
@@ -285,7 +285,8 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
285
285
  app.use((0, cors_1.default)());
286
286
  app.use((req, res, next) => {
287
287
  // Skip JSON parsing for SSE message endpoint - it needs raw body stream
288
- if (req.path === "/message") {
288
+ // Use req.url to handle query params like /message?sessionId=...
289
+ if (req.url.startsWith("/message")) {
289
290
  return next();
290
291
  }
291
292
  express_1.default.json()(req, res, next);
@@ -0,0 +1,184 @@
1
+ import { expect, test } from "@playwright/test";
2
+ import { ChildProcess, spawn } from "child_process";
3
+ import path from "path";
4
+
5
+ /**
6
+ * MCP SSE Transport Multi-Session Test
7
+ *
8
+ * This test validates:
9
+ * 1. Multiple concurrent SSE connections
10
+ * 2. Handling of missing X-Client-Name headers
11
+ * 3. Rapid connection establishment and teardown behavior
12
+ * 4. Graceful handling of malformed SSE requests and overall server health
13
+ *
14
+ * Purpose: Prevent SSE transport regressions identified in 0.12-0.14 releases
15
+ */
16
+ test.describe("MCP SSE Transport - Multi-Session", () => {
17
+ let serverProcess: ChildProcess;
18
+ const PORT = 3102; // Unique port to avoid conflicts
19
+
20
+ test.setTimeout(120000);
21
+
22
+ test.beforeAll(async () => {
23
+ // Start the server in http mode with in-memory DB
24
+ const serverPath = path.join(__dirname, "../dist/index.js");
25
+ serverProcess = spawn(
26
+ "node",
27
+ [
28
+ serverPath,
29
+ "--port",
30
+ PORT.toString(),
31
+ "--env",
32
+ "test",
33
+ "--db-path",
34
+ ":memory:",
35
+ ],
36
+ {
37
+ stdio: "pipe",
38
+ env: { ...process.env, OM_DB_PATH: ":memory:" },
39
+ },
40
+ );
41
+
42
+ // Wait for server to start
43
+ await new Promise<void>((resolve, reject) => {
44
+ let output = "";
45
+ const timeout = setTimeout(() => {
46
+ reject(new Error(`Server start timeout. Output: ${output}`));
47
+ }, 60000);
48
+
49
+ serverProcess.stderr?.on("data", (data) => {
50
+ const text = data.toString();
51
+ output += text;
52
+ console.log("[Server]", text);
53
+ if (text.includes(`CyberMem MCP running on http://localhost:${PORT}`)) {
54
+ clearTimeout(timeout);
55
+ resolve();
56
+ }
57
+ });
58
+
59
+ serverProcess.on("error", (err) => {
60
+ clearTimeout(timeout);
61
+ reject(err);
62
+ });
63
+ });
64
+ });
65
+
66
+ test.afterAll(() => {
67
+ if (serverProcess && !serverProcess.killed) {
68
+ serverProcess.kill();
69
+ }
70
+ });
71
+
72
+ test("should handle multiple concurrent SSE connections", async () => {
73
+ const connections: Array<{
74
+ response: Response;
75
+ reader: ReadableStreamDefaultReader<Uint8Array>;
76
+ }> = [];
77
+
78
+ // Open 3 concurrent SSE connections
79
+ for (let i = 0; i < 3; i++) {
80
+ const response = await fetch(`http://localhost:${PORT}/sse`, {
81
+ headers: { "X-Client-Name": `test-client-${i}` },
82
+ });
83
+ expect(response.status).toBe(200);
84
+ expect(response.headers.get("content-type")).toBe("text/event-stream");
85
+
86
+ const reader = response.body!.getReader();
87
+ connections.push({ response, reader });
88
+ }
89
+
90
+ // Verify all connections receive endpoint events
91
+ const decoder = new TextDecoder();
92
+ for (let i = 0; i < connections.length; i++) {
93
+ let endpointFound = false;
94
+ const { reader } = connections[i];
95
+
96
+ for (let j = 0; j < 3; j++) {
97
+ const { value, done } = await reader.read();
98
+ if (done) break;
99
+ const text = decoder.decode(value);
100
+ console.log(`[Connection ${i}]`, text);
101
+ if (text.includes("event: endpoint")) {
102
+ endpointFound = true;
103
+ break;
104
+ }
105
+ }
106
+
107
+ expect(endpointFound).toBe(true);
108
+ }
109
+
110
+ // Cleanup all connections
111
+ for (const { reader } of connections) {
112
+ await reader.cancel();
113
+ }
114
+
115
+ // Server should still be running
116
+ expect(serverProcess.exitCode).toBeNull();
117
+ });
118
+
119
+ test("should handle connection with missing X-Client-Name header", async () => {
120
+ // Connection should still work but may not have proper client identification
121
+ const response = await fetch(`http://localhost:${PORT}/sse`);
122
+ expect(response.status).toBe(200);
123
+ expect(response.headers.get("content-type")).toBe("text/event-stream");
124
+
125
+ const reader = response.body!.getReader();
126
+ const decoder = new TextDecoder();
127
+ let endpointFound = false;
128
+
129
+ for (let i = 0; i < 3; i++) {
130
+ const { value, done } = await reader.read();
131
+ if (done) break;
132
+ const text = decoder.decode(value);
133
+ if (text.includes("event: endpoint")) {
134
+ endpointFound = true;
135
+ break;
136
+ }
137
+ }
138
+
139
+ expect(endpointFound).toBe(true);
140
+ await reader.cancel();
141
+ });
142
+
143
+ test("should handle rapid connection establishment and teardown", async () => {
144
+ // Simulate client reconnection scenarios
145
+ for (let i = 0; i < 5; i++) {
146
+ const response = await fetch(`http://localhost:${PORT}/sse`, {
147
+ headers: { "X-Client-Name": `rapid-test-${i}` },
148
+ });
149
+ expect(response.status).toBe(200);
150
+
151
+ const reader = response.body!.getReader();
152
+
153
+ // Read one chunk then immediately disconnect
154
+ await reader.read();
155
+ await reader.cancel();
156
+ }
157
+
158
+ // Server should still be healthy
159
+ const healthResponse = await fetch(`http://localhost:${PORT}/health`);
160
+ expect(healthResponse.status).toBe(200);
161
+ });
162
+
163
+ test("should handle malformed SSE requests gracefully", async () => {
164
+ // POST to /sse should not crash the server
165
+ const postResponse = await fetch(`http://localhost:${PORT}/sse`, {
166
+ method: "POST",
167
+ });
168
+ // POST may return 404/405/400 depending on SDK version — just verify it's not 200
169
+ expect(postResponse.status).not.toBe(200);
170
+
171
+ // GET with suspicious headers should still work
172
+ const getResponse = await fetch(`http://localhost:${PORT}/sse`, {
173
+ method: "GET",
174
+ headers: { "X-Forwarded-For": "attacker.com" },
175
+ });
176
+ expect(getResponse.status).toBe(200);
177
+ const reader = getResponse.body!.getReader();
178
+ await reader.cancel();
179
+
180
+ // Server should still be healthy after both requests
181
+ const healthResponse = await fetch(`http://localhost:${PORT}/health`);
182
+ expect(healthResponse.status).toBe(200);
183
+ });
184
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cybermem/mcp",
3
- "version": "0.14.13",
3
+ "version": "0.14.14",
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
@@ -394,7 +394,8 @@ For full protocol: https://docs.cybermem.dev/agent-protocol`;
394
394
  app.use(cors());
395
395
  app.use((req, res, next) => {
396
396
  // Skip JSON parsing for SSE message endpoint - it needs raw body stream
397
- if (req.path === "/message") {
397
+ // Use req.url to handle query params like /message?sessionId=...
398
+ if (req.url.startsWith("/message")) {
398
399
  return next();
399
400
  }
400
401
  express.json()(req, res, next);