Skip to content

Commit b5df61b

Browse files
committed
capture sse responses for streamable http transport
1 parent 0fa46aa commit b5df61b

File tree

2 files changed

+89
-3
lines changed

2 files changed

+89
-3
lines changed

src/server/streamableHttp.test.ts

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { StreamableHTTPServerTransport } from "./streamableHttp.js";
33
import { JSONRPCMessage } from "../types.js";
44
import { Readable } from "node:stream";
55
import { randomUUID } from "node:crypto";
6+
import { McpServer } from "./mcp.js";
67
// Mock IncomingMessage
78
function createMockRequest(options: {
89
method: string;
@@ -1351,4 +1352,89 @@ describe("StreamableHTTPServerTransport", () => {
13511352
expect(onMessageMock).not.toHaveBeenCalledWith(requestBodyMessage);
13521353
});
13531354
});
1354-
});
1355+
1356+
describe("Connect to a MCP Server", () => {
1357+
it("should connect to a MCP Server", async () => {
1358+
const mcpServer = new McpServer({
1359+
name: "test-server",
1360+
version: "1.0",
1361+
});
1362+
1363+
mcpServer.tool("test-tool", async () => ({
1364+
content: [
1365+
{
1366+
type: "text",
1367+
text: "Hello, world!",
1368+
},
1369+
],
1370+
}));
1371+
1372+
await mcpServer.connect(transport);
1373+
1374+
const initializeMessage: JSONRPCMessage = {
1375+
jsonrpc: "2.0",
1376+
method: "initialize",
1377+
params: {
1378+
clientInfo: { name: "test-client", version: "1.0" },
1379+
protocolVersion: "2025-03-26"
1380+
},
1381+
id: "init-1",
1382+
};
1383+
1384+
const initializeReq = createMockRequest({
1385+
method: "POST",
1386+
headers: {
1387+
"content-type": "application/json",
1388+
"accept": "application/json, text/event-stream",
1389+
},
1390+
body: JSON.stringify(initializeMessage),
1391+
});
1392+
1393+
await transport.handleRequest(initializeReq, mockResponse);
1394+
1395+
expect(mockResponse.writeHead).toHaveBeenCalledWith(200, {"mcp-session-id": transport.sessionId});
1396+
1397+
mockResponse.end.mockClear();
1398+
mockResponse.writeHead.mockClear();
1399+
mockResponse.write.mockClear();
1400+
1401+
const listToolsMessage: JSONRPCMessage = {
1402+
jsonrpc: "2.0",
1403+
method: "tools/list",
1404+
id: "list-tools",
1405+
};
1406+
1407+
const listToolsReq = createMockRequest({
1408+
method: "POST",
1409+
headers: {
1410+
"content-type": "application/json",
1411+
"accept": "application/json, text/event-stream",
1412+
"mcp-session-id": transport.sessionId,
1413+
},
1414+
body: JSON.stringify(listToolsMessage),
1415+
});
1416+
1417+
await transport.handleRequest(listToolsReq, mockResponse);
1418+
1419+
expect(mockResponse.writeHead).toHaveBeenCalledWith(200, {
1420+
"Cache-Control": "no-cache",
1421+
"Connection": "keep-alive",
1422+
"Content-Type": "text/event-stream",
1423+
"mcp-session-id": transport.sessionId
1424+
});
1425+
1426+
await new Promise(resolve => setTimeout(resolve, 100));
1427+
1428+
const writeCalls = mockResponse.write.mock.calls
1429+
1430+
expect(writeCalls.length).toBeGreaterThan(0);
1431+
1432+
const lastWriteCall = writeCalls[writeCalls.length - 1]
1433+
expect(lastWriteCall.length).toBeGreaterThan(0);
1434+
1435+
const lastWriteCallData = lastWriteCall[0].toString()
1436+
expect(lastWriteCallData).toContain("event: message\ndata: ");
1437+
expect(lastWriteCallData).toContain('"name":"test-tool"');
1438+
});
1439+
});
1440+
});

src/shared/protocol.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ export abstract class Protocol<
369369
result,
370370
jsonrpc: "2.0",
371371
id: request.id,
372-
});
372+
}, { relatedRequestId: request.id });
373373
},
374374
(error) => {
375375
if (abortController.signal.aborted) {
@@ -385,7 +385,7 @@ export abstract class Protocol<
385385
: ErrorCode.InternalError,
386386
message: error.message ?? "Internal error",
387387
},
388-
});
388+
}, { relatedRequestId: request.id });
389389
},
390390
)
391391
.catch((error) =>

0 commit comments

Comments
 (0)