Skip to content

Commit 8775c1f

Browse files
committed
add integratoin test
1 parent 585dc06 commit 8775c1f

File tree

1 file changed

+265
-0
lines changed

1 file changed

+265
-0
lines changed
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
import { createServer, type Server } from 'node:http';
2+
import { AddressInfo } from 'node:net';
3+
import { randomUUID } from 'node:crypto';
4+
import { Client } from '../client/index.js';
5+
import { StreamableHTTPClientTransport } from '../client/streamableHttp.js';
6+
import { McpServer } from '../server/mcp.js';
7+
import { StreamableHTTPServerTransport } from '../server/streamableHttp.js';
8+
import { CallToolResultSchema, ListToolsResultSchema, ListResourcesResultSchema, ListPromptsResultSchema } from '../types.js';
9+
import { z } from 'zod';
10+
11+
describe('Streamable HTTP Transport Session Management', () => {
12+
// Function to set up the server with optional session management
13+
async function setupServer(withSessionManagement: boolean) {
14+
const server: Server = createServer();
15+
const mcpServer = new McpServer(
16+
{ name: 'test-server', version: '1.0.0' },
17+
{
18+
capabilities: {
19+
logging: {},
20+
tools: {},
21+
resources: {},
22+
prompts: {}
23+
}
24+
}
25+
);
26+
27+
// Add a simple resource
28+
mcpServer.resource(
29+
'test-resource',
30+
'/test',
31+
{ description: 'A test resource' },
32+
async () => ({
33+
contents: [{
34+
uri: '/test',
35+
text: 'This is a test resource content'
36+
}]
37+
})
38+
);
39+
40+
mcpServer.prompt(
41+
'test-prompt',
42+
'A test prompt',
43+
async () => ({
44+
messages: [{
45+
role: 'user',
46+
content: {
47+
type: 'text',
48+
text: 'This is a test prompt'
49+
}
50+
}]
51+
})
52+
);
53+
54+
mcpServer.tool(
55+
'greet',
56+
'A simple greeting tool',
57+
{
58+
name: z.string().describe('Name to greet').default('World'),
59+
},
60+
async ({ name }) => {
61+
return {
62+
content: [{ type: 'text', text: `Hello, ${name}!` }]
63+
};
64+
}
65+
);
66+
67+
// Create transport with or without session management
68+
const serverTransport = new StreamableHTTPServerTransport({
69+
sessionIdGenerator: withSessionManagement
70+
? () => randomUUID() // With session management, generate UUID
71+
: () => undefined // Without session management, return undefined
72+
});
73+
74+
await mcpServer.connect(serverTransport);
75+
76+
server.on('request', async (req, res) => {
77+
await serverTransport.handleRequest(req, res);
78+
});
79+
80+
// Start the server on a random port
81+
const baseUrl = await new Promise<URL>((resolve) => {
82+
server.listen(0, '127.0.0.1', () => {
83+
const addr = server.address() as AddressInfo;
84+
resolve(new URL(`http://127.0.0.1:${addr.port}`));
85+
});
86+
});
87+
88+
return { server, mcpServer, serverTransport, baseUrl };
89+
}
90+
91+
describe('Stateless Mode', () => {
92+
let server: Server;
93+
let mcpServer: McpServer;
94+
let serverTransport: StreamableHTTPServerTransport;
95+
let baseUrl: URL;
96+
97+
beforeEach(async () => {
98+
const setup = await setupServer(false);
99+
server = setup.server;
100+
mcpServer = setup.mcpServer;
101+
serverTransport = setup.serverTransport;
102+
baseUrl = setup.baseUrl;
103+
});
104+
105+
afterEach(async () => {
106+
// Clean up resources
107+
await mcpServer.close().catch(() => { });
108+
await serverTransport.close().catch(() => { });
109+
server.close();
110+
});
111+
112+
it('should operate without session management', async () => {
113+
// Create and connect a client
114+
const client = new Client({
115+
name: 'test-client',
116+
version: '1.0.0'
117+
});
118+
119+
const transport = new StreamableHTTPClientTransport(baseUrl);
120+
await client.connect(transport);
121+
122+
// Verify that no session ID was set
123+
expect(transport.sessionId).toBeUndefined();
124+
125+
// List available tools
126+
const toolsResult = await client.request({
127+
method: 'tools/list',
128+
params: {}
129+
}, ListToolsResultSchema);
130+
131+
// Verify tools are accessible
132+
expect(toolsResult.tools).toContainEqual(expect.objectContaining({
133+
name: 'greet'
134+
}));
135+
136+
// List available resources
137+
const resourcesResult = await client.request({
138+
method: 'resources/list',
139+
params: {}
140+
}, ListResourcesResultSchema);
141+
142+
// Verify resources result structure
143+
expect(resourcesResult).toHaveProperty('resources');
144+
145+
// List available prompts
146+
const promptsResult = await client.request({
147+
method: 'prompts/list',
148+
params: {}
149+
}, ListPromptsResultSchema);
150+
151+
// Verify prompts result structure
152+
expect(promptsResult).toHaveProperty('prompts');
153+
expect(promptsResult.prompts).toContainEqual(expect.objectContaining({
154+
name: 'test-prompt'
155+
}));
156+
157+
// Call the greeting tool
158+
const greetingResult = await client.request({
159+
method: 'tools/call',
160+
params: {
161+
name: 'greet',
162+
arguments: {
163+
name: 'Stateless Transport'
164+
}
165+
}
166+
}, CallToolResultSchema);
167+
168+
// Verify tool result
169+
expect(greetingResult.content).toEqual([
170+
{ type: 'text', text: 'Hello, Stateless Transport!' }
171+
]);
172+
173+
// Clean up
174+
await transport.close();
175+
});
176+
});
177+
178+
describe('Stateful Mode', () => {
179+
let server: Server;
180+
let mcpServer: McpServer;
181+
let serverTransport: StreamableHTTPServerTransport;
182+
let baseUrl: URL;
183+
184+
beforeEach(async () => {
185+
const setup = await setupServer(true);
186+
server = setup.server;
187+
mcpServer = setup.mcpServer;
188+
serverTransport = setup.serverTransport;
189+
baseUrl = setup.baseUrl;
190+
});
191+
192+
afterEach(async () => {
193+
// Clean up resources
194+
await mcpServer.close().catch(() => { });
195+
await serverTransport.close().catch(() => { });
196+
server.close();
197+
});
198+
199+
it('should operate with session management', async () => {
200+
// Create and connect a client
201+
const client = new Client({
202+
name: 'test-client',
203+
version: '1.0.0'
204+
});
205+
206+
const transport = new StreamableHTTPClientTransport(baseUrl);
207+
await client.connect(transport);
208+
209+
// Verify that a session ID was set
210+
expect(transport.sessionId).toBeDefined();
211+
expect(typeof transport.sessionId).toBe('string');
212+
213+
// List available tools
214+
const toolsResult = await client.request({
215+
method: 'tools/list',
216+
params: {}
217+
}, ListToolsResultSchema);
218+
219+
// Verify tools are accessible
220+
expect(toolsResult.tools).toContainEqual(expect.objectContaining({
221+
name: 'greet'
222+
}));
223+
224+
// List available resources
225+
const resourcesResult = await client.request({
226+
method: 'resources/list',
227+
params: {}
228+
}, ListResourcesResultSchema);
229+
230+
// Verify resources result structure
231+
expect(resourcesResult).toHaveProperty('resources');
232+
233+
// List available prompts
234+
const promptsResult = await client.request({
235+
method: 'prompts/list',
236+
params: {}
237+
}, ListPromptsResultSchema);
238+
239+
// Verify prompts result structure
240+
expect(promptsResult).toHaveProperty('prompts');
241+
expect(promptsResult.prompts).toContainEqual(expect.objectContaining({
242+
name: 'test-prompt'
243+
}));
244+
245+
// Call the greeting tool
246+
const greetingResult = await client.request({
247+
method: 'tools/call',
248+
params: {
249+
name: 'greet',
250+
arguments: {
251+
name: 'Stateful Transport'
252+
}
253+
}
254+
}, CallToolResultSchema);
255+
256+
// Verify tool result
257+
expect(greetingResult.content).toEqual([
258+
{ type: 'text', text: 'Hello, Stateful Transport!' }
259+
]);
260+
261+
// Clean up
262+
await transport.close();
263+
});
264+
});
265+
});

0 commit comments

Comments
 (0)