@cybermem/mcp 0.14.15 → 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 +12 -0
- package/dist/index.js +240 -413
- package/e2e/api.spec.ts +132 -186
- package/e2e/sse_transport.spec.ts +69 -29
- package/e2e/sse_transport_multi.spec.ts +73 -137
- package/e2e/stdio_attribution.spec.ts +74 -0
- package/e2e/utils/FastMCPHandshakeTransport.ts +183 -0
- package/package.json +6 -9
- package/scripts/postbuild.js +24 -0
- package/src/index.ts +317 -542
- package/src/openmemory-js.d.ts +6 -0
- package/test-handshake.ts +26 -0
package/e2e/api.spec.ts
CHANGED
|
@@ -1,259 +1,205 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
1
2
|
import { expect, test } from "@playwright/test";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import path from "path";
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
? process.env.MCP_URL.replace(/\/mcp$/, "")
|
|
5
|
-
: "http://localhost:8626";
|
|
6
|
+
import { FastMCPHandshakeTransport } from "./utils/FastMCPHandshakeTransport";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
BASE_URL.includes("localhost") || BASE_URL.includes("127.0.0.1");
|
|
10
|
-
const CYBERMEM_TOKEN = process.env.CYBERMEM_TOKEN || "";
|
|
11
|
-
|
|
12
|
-
// Helper to build headers with optional auth
|
|
13
|
-
function getHeaders(clientName: string): Record<string, string> {
|
|
14
|
-
const headers: Record<string, string> = { "X-Client-Name": clientName };
|
|
15
|
-
if (!isLocalhost && CYBERMEM_TOKEN) {
|
|
16
|
-
headers["X-API-Key"] = CYBERMEM_TOKEN;
|
|
17
|
-
}
|
|
18
|
-
return headers;
|
|
19
|
-
}
|
|
8
|
+
const PORT = 3102;
|
|
9
|
+
const BASE_URL = `http://localhost:${PORT}`;
|
|
20
10
|
|
|
21
11
|
// CRITICAL: MCP CRUD tests MUST run in serial order (each depends on the previous)
|
|
22
12
|
test.describe.configure({ mode: "serial" });
|
|
23
13
|
|
|
24
|
-
test.describe("MCP:E2E (
|
|
14
|
+
test.describe("MCP:E2E (Protocol Tool Calls)", () => {
|
|
15
|
+
let client: Client;
|
|
16
|
+
let transport: FastMCPHandshakeTransport;
|
|
25
17
|
let memoryId: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
18
|
+
let serverProcess: any;
|
|
19
|
+
|
|
20
|
+
test.beforeAll(async () => {
|
|
21
|
+
// Spawn server
|
|
22
|
+
const serverPath = path.join(__dirname, "../dist/index.js");
|
|
23
|
+
serverProcess = spawn(
|
|
24
|
+
"node",
|
|
25
|
+
[
|
|
26
|
+
serverPath,
|
|
27
|
+
"--port",
|
|
28
|
+
PORT.toString(),
|
|
29
|
+
"--env",
|
|
30
|
+
"test",
|
|
31
|
+
"--db-path",
|
|
32
|
+
":memory:",
|
|
33
|
+
],
|
|
34
|
+
{
|
|
35
|
+
stdio: "pipe",
|
|
36
|
+
env: { ...process.env, OM_DB_PATH: ":memory:" },
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
await new Promise<void>((resolve, reject) => {
|
|
41
|
+
serverProcess.stderr?.on("data", (data: any) => {
|
|
42
|
+
const output = data.toString();
|
|
43
|
+
if (
|
|
44
|
+
output.includes(`CyberMem MCP running on http://localhost:${PORT}`)
|
|
45
|
+
) {
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
serverProcess.on("error", reject);
|
|
50
|
+
setTimeout(() => reject(new Error("Server start timeout")), 60000);
|
|
51
|
+
});
|
|
52
|
+
|
|
35
53
|
console.log(`🔧 Testing against: ${BASE_URL}`);
|
|
36
54
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
// Initialize MCP Client
|
|
56
|
+
client = new Client(
|
|
57
|
+
{ name: "e2e-tester", version: "1.0.0" },
|
|
58
|
+
{ capabilities: {} },
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const sseUrl = new URL(`${BASE_URL}/mcp`);
|
|
62
|
+
const headers: Record<string, string> = {
|
|
63
|
+
"X-Client-Name": "antigravity-e2e",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
transport = new FastMCPHandshakeTransport(sseUrl, headers);
|
|
67
|
+
|
|
68
|
+
console.log(`🔗 Connecting to MCP via Handshake: ${sseUrl.toString()}`);
|
|
69
|
+
await client.connect(transport);
|
|
70
|
+
console.log("✅ MCP Client connected");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test.afterAll(async () => {
|
|
74
|
+
if (transport) {
|
|
75
|
+
await transport.close();
|
|
76
|
+
}
|
|
77
|
+
if (serverProcess) {
|
|
78
|
+
serverProcess.kill();
|
|
79
|
+
}
|
|
80
|
+
// Clean up port 3102 just in case
|
|
81
|
+
spawn("sh", ["-c", `lsof -ti:${PORT} | xargs kill -9 || true`]);
|
|
42
82
|
});
|
|
43
83
|
|
|
44
|
-
test("1. Create Memory (
|
|
84
|
+
test("1. Create Memory (add_memory)", async ({}, testInfo) => {
|
|
45
85
|
const payload = {
|
|
46
|
-
content: `E2E Verification ${new Date().toISOString()}`,
|
|
47
|
-
tags: ["e2e", "
|
|
86
|
+
content: `E2E Protocol Verification ${new Date().toISOString()}`,
|
|
87
|
+
tags: ["e2e", "protocol", "mcp"],
|
|
48
88
|
};
|
|
49
89
|
|
|
50
|
-
await test.step("📤
|
|
51
|
-
console.log("📤
|
|
52
|
-
console.log(" Payload:", JSON.stringify(payload, null, 2));
|
|
53
|
-
|
|
54
|
-
const response = await request.post(`${BASE_URL}/add`, {
|
|
55
|
-
data: payload,
|
|
56
|
-
headers: {
|
|
57
|
-
...getHeaders("antigravity-client"),
|
|
58
|
-
"X-Client-Version": "0.13.0",
|
|
59
|
-
},
|
|
60
|
-
timeout: 30000, // 30s timeout
|
|
61
|
-
});
|
|
90
|
+
await test.step("📤 Tool Call — add_memory", async () => {
|
|
91
|
+
console.log("📤 Calling tool: add_memory");
|
|
62
92
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
body = await response.json();
|
|
69
|
-
} catch {
|
|
70
|
-
const text = await response.text();
|
|
71
|
-
throw new Error(
|
|
72
|
-
`Non-JSON response (status ${status}): ${text.substring(0, 200)}`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
console.log(" Status:", status);
|
|
77
|
-
console.log(" Response:", JSON.stringify(body, null, 2));
|
|
78
|
-
|
|
79
|
-
crudLog.push({
|
|
80
|
-
operation: "CREATE",
|
|
81
|
-
endpoint: "POST /add",
|
|
82
|
-
payload,
|
|
83
|
-
status,
|
|
84
|
-
response: body,
|
|
93
|
+
const result = await client.callTool({
|
|
94
|
+
name: "add_memory",
|
|
95
|
+
arguments: payload,
|
|
85
96
|
});
|
|
86
97
|
|
|
87
|
-
|
|
98
|
+
console.log(" Result:", JSON.stringify(result, null, 2));
|
|
99
|
+
|
|
100
|
+
expect(result.isError).toBeFalsy();
|
|
101
|
+
const content = result.content as any[];
|
|
102
|
+
const body = JSON.parse(content[0].text);
|
|
88
103
|
expect(body.id).toBeTruthy();
|
|
89
104
|
memoryId = body.id;
|
|
90
105
|
console.log(` ✅ Memory ID: ${memoryId}`);
|
|
91
106
|
});
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
108
|
+
await testInfo.attach("📝 Tool Call — CREATE", {
|
|
109
|
+
body: `Tool: add_memory\n\nArguments:\n${JSON.stringify(
|
|
110
|
+
payload,
|
|
111
|
+
null,
|
|
112
|
+
2,
|
|
113
|
+
)}`,
|
|
96
114
|
contentType: "text/plain",
|
|
97
115
|
});
|
|
98
116
|
});
|
|
99
117
|
|
|
100
|
-
test("2. Read Memory (
|
|
101
|
-
await test.step("⏳ Wait — Vector Indexing Delay
|
|
118
|
+
test("2. Read Memory (query_memory)", async ({}, testInfo) => {
|
|
119
|
+
await test.step("⏳ Wait — Vector Indexing Delay", async () => {
|
|
102
120
|
await new Promise((r) => setTimeout(r, 1000));
|
|
103
121
|
});
|
|
104
122
|
|
|
105
|
-
const payload = { query: "Verification", k: 1 };
|
|
123
|
+
const payload = { query: "Protocol Verification", k: 1 };
|
|
106
124
|
|
|
107
|
-
await test.step("📤
|
|
108
|
-
console.log("📤
|
|
109
|
-
console.log(" Payload:", JSON.stringify(payload, null, 2));
|
|
125
|
+
await test.step("📤 Tool Call — query_memory", async () => {
|
|
126
|
+
console.log("📤 Calling tool: query_memory");
|
|
110
127
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
128
|
+
const result = await client.callTool({
|
|
129
|
+
name: "query_memory",
|
|
130
|
+
arguments: payload,
|
|
114
131
|
});
|
|
115
132
|
|
|
116
|
-
|
|
117
|
-
console.log(" Status:", response.status());
|
|
118
|
-
console.log(" Response:", JSON.stringify(body, null, 2));
|
|
133
|
+
console.log(" Result:", JSON.stringify(result, null, 2));
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
payload,
|
|
124
|
-
status: response.status(),
|
|
125
|
-
response: body,
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
expect(response.status()).toBe(200);
|
|
135
|
+
expect(result.isError).toBeFalsy();
|
|
136
|
+
const content = result.content as any[];
|
|
137
|
+
const body = JSON.parse(content[0].text);
|
|
129
138
|
expect(Array.isArray(body)).toBe(true);
|
|
139
|
+
expect(body.length).toBeGreaterThan(0);
|
|
130
140
|
console.log(` ✅ Returned ${body.length} result(s)`);
|
|
131
141
|
});
|
|
132
|
-
|
|
133
|
-
await testInfo.attach("📖 CRUD — READ", {
|
|
134
|
-
body: `Endpoint: POST /query\n\nRequest:\n${JSON.stringify(payload, null, 2)}\n\nResponse:\n${JSON.stringify(crudLog[crudLog.length - 1]?.response, null, 2)}`,
|
|
135
|
-
contentType: "text/plain",
|
|
136
|
-
});
|
|
137
142
|
});
|
|
138
143
|
|
|
139
|
-
test("3. Update Memory (
|
|
140
|
-
request,
|
|
141
|
-
}, testInfo) => {
|
|
144
|
+
test("3. Update Memory (update_memory)", async ({}, testInfo) => {
|
|
142
145
|
test.skip(!memoryId, "Skipped — memoryId was not created in Step 1");
|
|
143
146
|
|
|
144
147
|
const payload = {
|
|
145
|
-
|
|
146
|
-
|
|
148
|
+
id: memoryId,
|
|
149
|
+
content: `Updated E2E Context via Protocol ${new Date().toISOString()}`,
|
|
150
|
+
tags: ["e2e", "protocol", "updated"],
|
|
147
151
|
};
|
|
148
152
|
|
|
149
|
-
await test.step(
|
|
150
|
-
console.log(`📤
|
|
151
|
-
console.log(" Payload:", JSON.stringify(payload, null, 2));
|
|
153
|
+
await test.step("📤 Tool Call — update_memory", async () => {
|
|
154
|
+
console.log(`📤 Calling tool: update_memory for ${memoryId}`);
|
|
152
155
|
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
+
const result = await client.callTool({
|
|
157
|
+
name: "update_memory",
|
|
158
|
+
arguments: payload,
|
|
156
159
|
});
|
|
157
160
|
|
|
158
|
-
|
|
159
|
-
console.log(" Status:", response.status());
|
|
160
|
-
console.log(" Response:", JSON.stringify(body, null, 2));
|
|
161
|
+
console.log(" Result:", JSON.stringify(result, null, 2));
|
|
161
162
|
|
|
162
|
-
|
|
163
|
-
operation: "UPDATE",
|
|
164
|
-
endpoint: `PATCH /memory/${memoryId}`,
|
|
165
|
-
payload,
|
|
166
|
-
status: response.status(),
|
|
167
|
-
response: body,
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
expect(response.status()).toBe(200);
|
|
163
|
+
expect(result.isError).toBeFalsy();
|
|
171
164
|
console.log(` ✅ Memory updated successfully`);
|
|
172
165
|
});
|
|
173
|
-
|
|
174
|
-
await testInfo.attach("✏️ CRUD — UPDATE", {
|
|
175
|
-
body: `Endpoint: PATCH /memory/${memoryId}\n\nRequest:\n${JSON.stringify(payload, null, 2)}\n\nResponse:\n${JSON.stringify(crudLog[crudLog.length - 1]?.response, null, 2)}`,
|
|
176
|
-
contentType: "text/plain",
|
|
177
|
-
});
|
|
178
166
|
});
|
|
179
167
|
|
|
180
|
-
test("4. Reinforce Memory (
|
|
181
|
-
request,
|
|
182
|
-
}, testInfo) => {
|
|
168
|
+
test("4. Reinforce Memory (reinforce_memory)", async ({}, testInfo) => {
|
|
183
169
|
test.skip(!memoryId, "Skipped — memoryId was not created in Step 1");
|
|
184
170
|
|
|
185
|
-
const payload = { boost: 0.5 };
|
|
171
|
+
const payload = { id: memoryId, boost: 0.5 };
|
|
186
172
|
|
|
187
|
-
await test.step(
|
|
188
|
-
console.log(`📤
|
|
189
|
-
console.log(" Payload:", JSON.stringify(payload, null, 2));
|
|
173
|
+
await test.step("📤 Tool Call — reinforce_memory", async () => {
|
|
174
|
+
console.log(`📤 Calling tool: reinforce_memory for ${memoryId}`);
|
|
190
175
|
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
data: payload,
|
|
195
|
-
headers: getHeaders("antigravity-client"),
|
|
196
|
-
},
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
const body = await response.json();
|
|
200
|
-
console.log(" Status:", response.status());
|
|
201
|
-
console.log(" Response:", JSON.stringify(body, null, 2));
|
|
202
|
-
|
|
203
|
-
crudLog.push({
|
|
204
|
-
operation: "REINFORCE",
|
|
205
|
-
endpoint: `POST /memory/${memoryId}/reinforce`,
|
|
206
|
-
payload,
|
|
207
|
-
status: response.status(),
|
|
208
|
-
response: body,
|
|
176
|
+
const result = await client.callTool({
|
|
177
|
+
name: "reinforce_memory",
|
|
178
|
+
arguments: payload,
|
|
209
179
|
});
|
|
210
180
|
|
|
211
|
-
|
|
212
|
-
console.log(` ✅ Memory reinforced successfully`);
|
|
213
|
-
});
|
|
181
|
+
console.log(" Result:", JSON.stringify(result, null, 2));
|
|
214
182
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
contentType: "text/plain",
|
|
183
|
+
expect(result.isError).toBeFalsy();
|
|
184
|
+
console.log(` ✅ Memory reinforced successfully`);
|
|
218
185
|
});
|
|
219
186
|
});
|
|
220
187
|
|
|
221
|
-
test("5. Delete Memory (
|
|
222
|
-
request,
|
|
223
|
-
}, testInfo) => {
|
|
188
|
+
test("5. Delete Memory (delete_memory)", async ({}, testInfo) => {
|
|
224
189
|
test.skip(!memoryId, "Skipped — memoryId was not created in Step 1");
|
|
225
190
|
|
|
226
|
-
await test.step(
|
|
227
|
-
console.log(`📤
|
|
191
|
+
await test.step("📤 Tool Call — delete_memory", async () => {
|
|
192
|
+
console.log(`📤 Calling tool: delete_memory for ${memoryId}`);
|
|
228
193
|
|
|
229
|
-
const
|
|
230
|
-
|
|
194
|
+
const result = await client.callTool({
|
|
195
|
+
name: "delete_memory",
|
|
196
|
+
arguments: { id: memoryId },
|
|
231
197
|
});
|
|
232
198
|
|
|
233
|
-
|
|
234
|
-
console.log(" Status:", response.status());
|
|
235
|
-
console.log(" Response:", JSON.stringify(body, null, 2));
|
|
199
|
+
console.log(" Result:", JSON.stringify(result, null, 2));
|
|
236
200
|
|
|
237
|
-
|
|
238
|
-
operation: "DELETE",
|
|
239
|
-
endpoint: `DELETE /memory/${memoryId}`,
|
|
240
|
-
status: response.status(),
|
|
241
|
-
response: body,
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
expect(response.status()).toBe(200);
|
|
201
|
+
expect(result.isError).toBeFalsy();
|
|
245
202
|
console.log(` ✅ Memory deleted successfully`);
|
|
246
203
|
});
|
|
247
|
-
|
|
248
|
-
await testInfo.attach("🗑️ CRUD — DELETE", {
|
|
249
|
-
body: `Endpoint: DELETE /memory/${memoryId}\n\nResponse:\n${JSON.stringify(crudLog[crudLog.length - 1]?.response, null, 2)}\n\n--- FULL CRUD LOG ---\n${crudLog.map((c) => `${c.operation}: ${c.endpoint} → ${c.status}`).join("\n")}`,
|
|
250
|
-
contentType: "text/plain",
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// Attach full CRUD summary at the end
|
|
254
|
-
await testInfo.attach("📊 CRUD Lifecycle Complete", {
|
|
255
|
-
body: `Memory ID: ${memoryId}\n\nOperations Performed:\n${crudLog.map((c) => `✅ ${c.operation}: ${c.endpoint} → HTTP ${c.status}`).join("\n")}\n\nStorage State: CLEANED (memory deleted)`,
|
|
256
|
-
contentType: "text/plain",
|
|
257
|
-
});
|
|
258
204
|
});
|
|
259
205
|
});
|
|
@@ -2,14 +2,13 @@ import { expect, test } from "@playwright/test";
|
|
|
2
2
|
import { ChildProcess, spawn } from "child_process";
|
|
3
3
|
import path from "path";
|
|
4
4
|
|
|
5
|
-
test.describe("MCP SSE Transport", () => {
|
|
5
|
+
test.describe("MCP SSE Transport Handshake", () => {
|
|
6
6
|
let serverProcess: ChildProcess;
|
|
7
|
-
const PORT = 3101;
|
|
7
|
+
const PORT = 3101;
|
|
8
8
|
|
|
9
9
|
test.setTimeout(120000);
|
|
10
10
|
|
|
11
11
|
test.beforeAll(async () => {
|
|
12
|
-
// Start the server in http mode
|
|
13
12
|
const serverPath = path.join(__dirname, "../dist/index.js");
|
|
14
13
|
serverProcess = spawn(
|
|
15
14
|
"node",
|
|
@@ -28,11 +27,9 @@ test.describe("MCP SSE Transport", () => {
|
|
|
28
27
|
},
|
|
29
28
|
);
|
|
30
29
|
|
|
31
|
-
// Wait for server to start
|
|
32
30
|
await new Promise<void>((resolve, reject) => {
|
|
33
31
|
serverProcess.stderr?.on("data", (data) => {
|
|
34
32
|
const output = data.toString();
|
|
35
|
-
console.log("[Server]", output);
|
|
36
33
|
if (
|
|
37
34
|
output.includes(`CyberMem MCP running on http://localhost:${PORT}`)
|
|
38
35
|
) {
|
|
@@ -45,37 +42,80 @@ test.describe("MCP SSE Transport", () => {
|
|
|
45
42
|
});
|
|
46
43
|
|
|
47
44
|
test.afterAll(() => {
|
|
48
|
-
serverProcess.
|
|
45
|
+
if (serverProcess && !serverProcess.killed) {
|
|
46
|
+
serverProcess.kill();
|
|
47
|
+
}
|
|
49
48
|
});
|
|
50
49
|
|
|
51
|
-
test("should
|
|
52
|
-
const
|
|
53
|
-
expect(response.status).toBe(200);
|
|
54
|
-
expect(response.headers.get("content-type")).toBe("text/event-stream");
|
|
50
|
+
test("should establishment session and call tool via handshake", async () => {
|
|
51
|
+
const CLIENT_NAME = "e2e-handshake-tester";
|
|
55
52
|
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
|
|
53
|
+
// 1. Handshake (POST initialize)
|
|
54
|
+
const initResponse = await fetch(`http://localhost:${PORT}/mcp`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Accept: "application/json, text/event-stream",
|
|
59
|
+
"X-Client-Name": CLIENT_NAME,
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify({
|
|
62
|
+
jsonrpc: "2.0",
|
|
63
|
+
id: 1,
|
|
64
|
+
method: "initialize",
|
|
65
|
+
params: {
|
|
66
|
+
protocolVersion: "2024-11-05",
|
|
67
|
+
capabilities: {},
|
|
68
|
+
clientInfo: { name: "e2e", version: "1.0.0" },
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
73
|
+
expect(initResponse.status).toBe(200);
|
|
74
|
+
const sessionId = initResponse.headers.get("mcp-session-id");
|
|
75
|
+
const initBody = await initResponse.json();
|
|
76
|
+
console.log(
|
|
77
|
+
"[Test] Init Headers:",
|
|
78
|
+
JSON.stringify(Object.fromEntries(initResponse.headers.entries())),
|
|
79
|
+
);
|
|
80
|
+
console.log("[Test] Init Body:", JSON.stringify(initBody));
|
|
81
|
+
expect(sessionId).toBeDefined();
|
|
62
82
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
// 2. Open Stream (GET)
|
|
84
|
+
const streamResponse = await fetch(`http://localhost:${PORT}/mcp`, {
|
|
85
|
+
headers: {
|
|
86
|
+
Accept: "text/event-stream",
|
|
87
|
+
"mcp-session-id": sessionId!,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
expect(streamResponse.status).toBe(200);
|
|
91
|
+
const reader = streamResponse.body?.getReader();
|
|
72
92
|
|
|
73
|
-
|
|
93
|
+
// 3. Call Tool (POST)
|
|
94
|
+
const toolResponse = await fetch(`http://localhost:${PORT}/mcp`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
Accept: "application/json, text/event-stream",
|
|
99
|
+
"mcp-session-id": sessionId!,
|
|
100
|
+
"X-Client-Name": CLIENT_NAME,
|
|
101
|
+
},
|
|
102
|
+
body: JSON.stringify({
|
|
103
|
+
jsonrpc: "2.0",
|
|
104
|
+
id: 2,
|
|
105
|
+
method: "tools/call",
|
|
106
|
+
params: {
|
|
107
|
+
name: "add_memory",
|
|
108
|
+
arguments: { content: "Handshake Test Memory" },
|
|
109
|
+
},
|
|
110
|
+
}),
|
|
111
|
+
});
|
|
74
112
|
|
|
75
|
-
|
|
76
|
-
await
|
|
113
|
+
expect(toolResponse.status).toBe(200);
|
|
114
|
+
const toolBody = await toolResponse.json();
|
|
115
|
+
console.log("[Test] Tool Result:", JSON.stringify(toolBody));
|
|
116
|
+
expect(toolBody.result).toBeDefined();
|
|
77
117
|
|
|
78
|
-
//
|
|
79
|
-
|
|
118
|
+
// Cleanup
|
|
119
|
+
await reader?.cancel();
|
|
80
120
|
});
|
|
81
121
|
});
|