@@ -2,15 +2,15 @@ import { IncomingMessage, ServerResponse } from "node:http";
2
2
import { StreamableHTTPServerTransport } from "./streamable-http.js" ;
3
3
import { JSONRPCMessage } from "../types.js" ;
4
4
import { Readable } from "node:stream" ;
5
-
5
+ import { randomUUID } from "node:crypto" ;
6
6
// Mock IncomingMessage
7
7
function createMockRequest ( options : {
8
8
method : string ;
9
9
headers : Record < string , string | string [ ] | undefined > ;
10
10
body ?: string ;
11
11
} ) : IncomingMessage {
12
12
const readable = new Readable ( ) ;
13
- readable . _read = ( ) => { } ;
13
+ readable . _read = ( ) => { } ;
14
14
if ( options . body ) {
15
15
readable . push ( options . body ) ;
16
16
readable . push ( null ) ;
@@ -37,12 +37,13 @@ function createMockResponse(): jest.Mocked<ServerResponse> {
37
37
}
38
38
39
39
describe ( "StreamableHTTPServerTransport" , ( ) => {
40
- const endpoint = "/mcp" ;
41
40
let transport : StreamableHTTPServerTransport ;
42
41
let mockResponse : jest . Mocked < ServerResponse > ;
43
42
44
43
beforeEach ( ( ) => {
45
- transport = new StreamableHTTPServerTransport ( endpoint ) ;
44
+ transport = new StreamableHTTPServerTransport ( {
45
+ sessionId : randomUUID ( ) ,
46
+ } ) ;
46
47
mockResponse = createMockResponse ( ) ;
47
48
} ) ;
48
49
@@ -61,7 +62,7 @@ describe("StreamableHTTPServerTransport", () => {
61
62
const initializeMessage : JSONRPCMessage = {
62
63
jsonrpc : "2.0" ,
63
64
method : "initialize" ,
64
- params : {
65
+ params : {
65
66
clientInfo : { name : "test-client" , version : "1.0" } ,
66
67
protocolVersion : "2025-03-26"
67
68
} ,
@@ -119,49 +120,13 @@ describe("StreamableHTTPServerTransport", () => {
119
120
expect ( mockResponse . end ) . toHaveBeenCalledWith ( expect . stringContaining ( '"jsonrpc":"2.0"' ) ) ;
120
121
expect ( mockResponse . end ) . toHaveBeenCalledWith ( expect . stringContaining ( '"message":"Bad Request: Mcp-Session-Id header is required"' ) ) ;
121
122
} ) ;
122
-
123
- it ( "should always include session ID in initialization response even in stateless mode" , async ( ) => {
124
- // Create a stateless transport for this test
125
- const statelessTransport = new StreamableHTTPServerTransport ( endpoint , { enableSessionManagement : false } ) ;
126
-
127
- // Create an initialization request
128
- const initializeMessage : JSONRPCMessage = {
129
- jsonrpc : "2.0" ,
130
- method : "initialize" ,
131
- params : {
132
- clientInfo : { name : "test-client" , version : "1.0" } ,
133
- protocolVersion : "2025-03-26"
134
- } ,
135
- id : "init-1" ,
136
- } ;
137
-
138
- const req = createMockRequest ( {
139
- method : "POST" ,
140
- headers : {
141
- "content-type" : "application/json" ,
142
- "accept" : "application/json" ,
143
- } ,
144
- body : JSON . stringify ( initializeMessage ) ,
145
- } ) ;
146
-
147
- await statelessTransport . handleRequest ( req , mockResponse ) ;
148
-
149
- // In stateless mode, session ID should also be included for initialize responses
150
- expect ( mockResponse . writeHead ) . toHaveBeenCalledWith (
151
- 200 ,
152
- expect . objectContaining ( {
153
- "mcp-session-id" : statelessTransport . sessionId ,
154
- } )
155
- ) ;
156
- } ) ;
157
123
} ) ;
158
-
159
124
describe ( "Stateless Mode" , ( ) => {
160
125
let statelessTransport : StreamableHTTPServerTransport ;
161
126
let mockResponse : jest . Mocked < ServerResponse > ;
162
127
163
128
beforeEach ( ( ) => {
164
- statelessTransport = new StreamableHTTPServerTransport ( endpoint , { enableSessionManagement : false } ) ;
129
+ statelessTransport = new StreamableHTTPServerTransport ( { sessionId : undefined } ) ;
165
130
mockResponse = createMockResponse ( ) ;
166
131
} ) ;
167
132
@@ -268,7 +233,7 @@ describe("StreamableHTTPServerTransport", () => {
268
233
} ) ;
269
234
270
235
await statelessTransport . handleRequest ( req2 , mockResponse ) ;
271
-
236
+
272
237
// Should still succeed
273
238
expect ( mockResponse . writeHead ) . toHaveBeenCalledWith (
274
239
200 ,
@@ -278,12 +243,12 @@ describe("StreamableHTTPServerTransport", () => {
278
243
) ;
279
244
} ) ;
280
245
281
- it ( "should handle initialization requests properly in both modes " , async ( ) => {
246
+ it ( "should handle initialization requests properly in statefull mode " , async ( ) => {
282
247
// Initialize message that would typically be sent during initialization
283
248
const initializeMessage : JSONRPCMessage = {
284
249
jsonrpc : "2.0" ,
285
250
method : "initialize" ,
286
- params : {
251
+ params : {
287
252
clientInfo : { name : "test-client" , version : "1.0" } ,
288
253
protocolVersion : "2025-03-26"
289
254
} ,
@@ -301,17 +266,27 @@ describe("StreamableHTTPServerTransport", () => {
301
266
} ) ;
302
267
303
268
await transport . handleRequest ( statefulReq , mockResponse ) ;
304
-
269
+
305
270
// In stateful mode, session ID should be included in the response header
306
271
expect ( mockResponse . writeHead ) . toHaveBeenCalledWith (
307
272
200 ,
308
273
expect . objectContaining ( {
309
274
"mcp-session-id" : transport . sessionId ,
310
275
} )
311
276
) ;
277
+ } ) ;
312
278
313
- // Reset mocks for stateless test
314
- mockResponse . writeHead . mockClear ( ) ;
279
+ it ( "should handle initialization requests properly in stateless mode" , async ( ) => {
280
+ // Initialize message that would typically be sent during initialization
281
+ const initializeMessage : JSONRPCMessage = {
282
+ jsonrpc : "2.0" ,
283
+ method : "initialize" ,
284
+ params : {
285
+ clientInfo : { name : "test-client" , version : "1.0" } ,
286
+ protocolVersion : "2025-03-26"
287
+ } ,
288
+ id : "init-1" ,
289
+ } ;
315
290
316
291
// Test stateless transport
317
292
const statelessReq = createMockRequest ( {
@@ -324,10 +299,11 @@ describe("StreamableHTTPServerTransport", () => {
324
299
} ) ;
325
300
326
301
await statelessTransport . handleRequest ( statelessReq , mockResponse ) ;
327
-
302
+
328
303
// In stateless mode, session ID should also be included for initialize responses
329
304
const headers = mockResponse . writeHead . mock . calls [ 0 ] [ 1 ] ;
330
- expect ( headers ) . toHaveProperty ( "mcp-session-id" , statelessTransport . sessionId ) ;
305
+ expect ( headers ) . not . toHaveProperty ( "mcp-session-id" ) ;
306
+
331
307
} ) ;
332
308
} ) ;
333
309
@@ -519,14 +495,14 @@ describe("StreamableHTTPServerTransport", () => {
519
495
520
496
// Send a message to first connection
521
497
const message1 : JSONRPCMessage = {
522
- jsonrpc : "2.0" ,
523
- method : "test1" ,
524
- params : { } ,
498
+ jsonrpc : "2.0" ,
499
+ method : "test1" ,
500
+ params : { } ,
525
501
id : 1
526
502
} ;
527
-
503
+
528
504
await transport . send ( message1 ) ;
529
-
505
+
530
506
// Get message ID (captured from write call)
531
507
const writeCall = mockResponse . write . mock . calls [ 0 ] [ 0 ] as string ;
532
508
const idMatch = writeCall . match ( / i d : ( [ a - f 0 - 9 - ] + ) / ) ;
@@ -550,12 +526,12 @@ describe("StreamableHTTPServerTransport", () => {
550
526
551
527
// Send a second message
552
528
const message2 : JSONRPCMessage = {
553
- jsonrpc : "2.0" ,
554
- method : "test2" ,
555
- params : { } ,
529
+ jsonrpc : "2.0" ,
530
+ method : "test2" ,
531
+ params : { } ,
556
532
id : 2
557
533
} ;
558
-
534
+
559
535
await transport . send ( message2 ) ;
560
536
561
537
// Verify the second message was received by both connections
@@ -596,7 +572,7 @@ describe("StreamableHTTPServerTransport", () => {
596
572
params : { } ,
597
573
id : "test-id" ,
598
574
} ;
599
-
575
+
600
576
const reqPost = createMockRequest ( {
601
577
method : "POST" ,
602
578
headers : {
@@ -605,28 +581,28 @@ describe("StreamableHTTPServerTransport", () => {
605
581
} ,
606
582
body : JSON . stringify ( requestMessage ) ,
607
583
} ) ;
608
-
584
+
609
585
await transport . handleRequest ( reqPost , mockResponse1 ) ;
610
-
586
+
611
587
// Send a response with matching ID
612
588
const responseMessage : JSONRPCMessage = {
613
589
jsonrpc : "2.0" ,
614
590
result : { success : true } ,
615
591
id : "test-id" ,
616
592
} ;
617
-
593
+
618
594
await transport . send ( responseMessage ) ;
619
-
595
+
620
596
// Verify response was sent to the right connection
621
597
expect ( mockResponse1 . write ) . toHaveBeenCalledWith (
622
598
expect . stringContaining ( JSON . stringify ( responseMessage ) )
623
599
) ;
624
-
600
+
625
601
// Check if write was called with this exact message on the second connection
626
- const writeCallsOnSecondConn = mockResponse2 . write . mock . calls . filter ( call =>
602
+ const writeCallsOnSecondConn = mockResponse2 . write . mock . calls . filter ( call =>
627
603
typeof call [ 0 ] === 'string' && call [ 0 ] . includes ( JSON . stringify ( responseMessage ) )
628
604
) ;
629
-
605
+
630
606
// Verify the response wasn't broadcast to all connections
631
607
expect ( writeCallsOnSecondConn . length ) . toBe ( 0 ) ;
632
608
} ) ;
@@ -680,7 +656,7 @@ describe("StreamableHTTPServerTransport", () => {
680
656
const message : JSONRPCMessage = {
681
657
jsonrpc : "2.0" ,
682
658
method : "initialize" ,
683
- params : {
659
+ params : {
684
660
clientInfo : { name : "test-client" , version : "1.0" } ,
685
661
protocolVersion : "2025-03-26"
686
662
} ,
@@ -715,17 +691,17 @@ describe("StreamableHTTPServerTransport", () => {
715
691
716
692
it ( "should handle pre-parsed batch messages" , async ( ) => {
717
693
const batchMessages : JSONRPCMessage [ ] = [
718
- {
719
- jsonrpc : "2.0" ,
720
- method : "method1" ,
694
+ {
695
+ jsonrpc : "2.0" ,
696
+ method : "method1" ,
721
697
params : { data : "test1" } ,
722
- id : "batch1"
698
+ id : "batch1"
723
699
} ,
724
- {
725
- jsonrpc : "2.0" ,
726
- method : "method2" ,
700
+ {
701
+ jsonrpc : "2.0" ,
702
+ method : "method2" ,
727
703
params : { data : "test2" } ,
728
- id : "batch2"
704
+ id : "batch2"
729
705
} ,
730
706
] ;
731
707
@@ -800,7 +776,7 @@ describe("StreamableHTTPServerTransport", () => {
800
776
let mockResponse : jest . Mocked < ServerResponse > ;
801
777
802
778
beforeEach ( ( ) => {
803
- transportWithHeaders = new StreamableHTTPServerTransport ( endpoint , { customHeaders } ) ;
779
+ transportWithHeaders = new StreamableHTTPServerTransport ( { sessionId : randomUUID ( ) , customHeaders } ) ;
804
780
mockResponse = createMockResponse ( ) ;
805
781
} ) ;
806
782
@@ -875,9 +851,10 @@ describe("StreamableHTTPServerTransport", () => {
875
851
} ) ;
876
852
877
853
it ( "should not override essential headers with custom headers" , async ( ) => {
878
- const transportWithConflictingHeaders = new StreamableHTTPServerTransport ( endpoint , {
854
+ const transportWithConflictingHeaders = new StreamableHTTPServerTransport ( {
855
+ sessionId : randomUUID ( ) ,
879
856
customHeaders : {
880
- "Content-Type" : "text/plain" , // 尝试覆盖必要的 Content-Type 头
857
+ "Content-Type" : "text/plain" ,
881
858
"X-Custom-Header" : "custom-value"
882
859
}
883
860
} ) ;
@@ -902,8 +879,10 @@ describe("StreamableHTTPServerTransport", () => {
902
879
} ) ;
903
880
904
881
it ( "should work with empty custom headers" , async ( ) => {
905
- const transportWithoutHeaders = new StreamableHTTPServerTransport ( endpoint ) ;
906
-
882
+ const transportWithoutHeaders = new StreamableHTTPServerTransport ( {
883
+ sessionId : randomUUID ( ) ,
884
+ } ) ;
885
+
907
886
const req = createMockRequest ( {
908
887
method : "GET" ,
909
888
headers : {
0 commit comments