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