@@ -858,6 +858,293 @@ describe("WorkerTransport", () => {
858858 } ) ;
859859 } ) ;
860860
861+ describe ( "Client Capabilities Persistence (Serverless Restart)" , ( ) => {
862+ it ( "should persist initializeParams when client sends capabilities" , async ( ) => {
863+ const server = createTestServer ( ) ;
864+ let storedState : TransportState | undefined ;
865+
866+ const mockStorage = {
867+ get : async ( ) => storedState ,
868+ set : async ( state : TransportState ) => {
869+ storedState = state ;
870+ }
871+ } ;
872+
873+ const transport = await setupTransport ( server , {
874+ sessionIdGenerator : ( ) => "test-session" ,
875+ storage : mockStorage ,
876+ enableJsonResponse : true
877+ } ) ;
878+
879+ const request = new Request ( "http://example.com/" , {
880+ method : "POST" ,
881+ headers : {
882+ "Content-Type" : "application/json" ,
883+ Accept : "application/json, text/event-stream"
884+ } ,
885+ body : JSON . stringify ( {
886+ jsonrpc : "2.0" ,
887+ id : "1" ,
888+ method : "initialize" ,
889+ params : {
890+ capabilities : {
891+ elicitation : { form : { } }
892+ } ,
893+ clientInfo : { name : "test-client" , version : "1.0" } ,
894+ protocolVersion : "2025-06-18"
895+ }
896+ } )
897+ } ) ;
898+
899+ const response = await transport . handleRequest ( request ) ;
900+ await response . json ( ) ;
901+
902+ expect ( response . status ) . toBe ( 200 ) ;
903+ expect ( storedState ) . toBeDefined ( ) ;
904+ expect ( storedState ?. initializeParams ) . toBeDefined ( ) ;
905+ expect (
906+ storedState ?. initializeParams ?. capabilities ?. elicitation ?. form
907+ ) . toBeDefined ( ) ;
908+ expect ( storedState ?. initializeParams ?. clientInfo ) . toEqual ( {
909+ name : "test-client" ,
910+ version : "1.0"
911+ } ) ;
912+ expect ( storedState ?. initializeParams ?. protocolVersion ) . toBe ( "2025-06-18" ) ;
913+ } ) ;
914+
915+ it ( "should restore client capabilities on Server instance after restart" , async ( ) => {
916+ // Phase 1: Initialize with capabilities
917+ let storedState : TransportState | undefined ;
918+ const mockStorage = {
919+ get : async ( ) => storedState ,
920+ set : async ( state : TransportState ) => {
921+ storedState = state ;
922+ }
923+ } ;
924+
925+ const server1 = createTestServer ( ) ;
926+ const transport1 = await setupTransport ( server1 , {
927+ sessionIdGenerator : ( ) => "test-session" ,
928+ storage : mockStorage ,
929+ enableJsonResponse : true
930+ } ) ;
931+
932+ const initRequest = new Request ( "http://example.com/" , {
933+ method : "POST" ,
934+ headers : {
935+ "Content-Type" : "application/json" ,
936+ Accept : "application/json, text/event-stream"
937+ } ,
938+ body : JSON . stringify ( {
939+ jsonrpc : "2.0" ,
940+ id : "1" ,
941+ method : "initialize" ,
942+ params : {
943+ capabilities : {
944+ elicitation : { form : { } }
945+ } ,
946+ clientInfo : { name : "test-client" , version : "1.0" } ,
947+ protocolVersion : "2025-06-18"
948+ }
949+ } )
950+ } ) ;
951+
952+ await transport1 . handleRequest ( initRequest ) ;
953+
954+ // Verify server1 has capabilities
955+ expect (
956+ server1 . server . getClientCapabilities ( ) ?. elicitation ?. form
957+ ) . toBeDefined ( ) ;
958+
959+ // Phase 2: Simulate serverless restart with NEW instances
960+ const server2 = createTestServer ( ) ;
961+ const transport2 = await setupTransport ( server2 , {
962+ sessionIdGenerator : ( ) => "test-session" ,
963+ storage : mockStorage ,
964+ enableJsonResponse : true
965+ } ) ;
966+
967+ // Trigger state restoration by making a request
968+ const listRequest = new Request ( "http://example.com/" , {
969+ method : "POST" ,
970+ headers : {
971+ "Content-Type" : "application/json" ,
972+ Accept : "application/json, text/event-stream" ,
973+ "mcp-session-id" : "test-session"
974+ } ,
975+ body : JSON . stringify ( {
976+ jsonrpc : "2.0" ,
977+ id : "2" ,
978+ method : "tools/list" ,
979+ params : { }
980+ } )
981+ } ) ;
982+
983+ await transport2 . handleRequest ( listRequest ) ;
984+
985+ // Verify capabilities were restored on server2
986+ expect ( transport2 . sessionId ) . toBe ( "test-session" ) ;
987+ expect ( server2 . server . getClientCapabilities ( ) ) . toBeDefined ( ) ;
988+ expect (
989+ server2 . server . getClientCapabilities ( ) ?. elicitation ?. form
990+ ) . toBeDefined ( ) ;
991+ } ) ;
992+
993+ it ( "should restore clientInfo on Server instance after restart" , async ( ) => {
994+ let storedState : TransportState | undefined ;
995+ const mockStorage = {
996+ get : async ( ) => storedState ,
997+ set : async ( state : TransportState ) => {
998+ storedState = state ;
999+ }
1000+ } ;
1001+
1002+ const server1 = createTestServer ( ) ;
1003+ const transport1 = await setupTransport ( server1 , {
1004+ sessionIdGenerator : ( ) => "test-session" ,
1005+ storage : mockStorage ,
1006+ enableJsonResponse : true
1007+ } ) ;
1008+
1009+ const initRequest = new Request ( "http://example.com/" , {
1010+ method : "POST" ,
1011+ headers : {
1012+ "Content-Type" : "application/json" ,
1013+ Accept : "application/json, text/event-stream"
1014+ } ,
1015+ body : JSON . stringify ( {
1016+ jsonrpc : "2.0" ,
1017+ id : "1" ,
1018+ method : "initialize" ,
1019+ params : {
1020+ capabilities : { } ,
1021+ clientInfo : { name : "my-client" , version : "2.0" } ,
1022+ protocolVersion : "2025-06-18"
1023+ }
1024+ } )
1025+ } ) ;
1026+
1027+ await transport1 . handleRequest ( initRequest ) ;
1028+
1029+ // Simulate restart
1030+ const server2 = createTestServer ( ) ;
1031+ const transport2 = await setupTransport ( server2 , {
1032+ sessionIdGenerator : ( ) => "test-session" ,
1033+ storage : mockStorage ,
1034+ enableJsonResponse : true
1035+ } ) ;
1036+
1037+ const listRequest = new Request ( "http://example.com/" , {
1038+ method : "POST" ,
1039+ headers : {
1040+ "Content-Type" : "application/json" ,
1041+ Accept : "application/json, text/event-stream" ,
1042+ "mcp-session-id" : "test-session"
1043+ } ,
1044+ body : JSON . stringify ( {
1045+ jsonrpc : "2.0" ,
1046+ id : "2" ,
1047+ method : "tools/list" ,
1048+ params : { }
1049+ } )
1050+ } ) ;
1051+
1052+ await transport2 . handleRequest ( listRequest ) ;
1053+
1054+ // Verify clientInfo was restored
1055+ expect ( server2 . server . getClientVersion ( ) ) . toEqual ( {
1056+ name : "my-client" ,
1057+ version : "2.0"
1058+ } ) ;
1059+ } ) ;
1060+
1061+ it ( "should handle old storage format without initializeParams (backward compatibility)" , async ( ) => {
1062+ // Simulate old stored state without initializeParams field
1063+ const oldState : TransportState = {
1064+ sessionId : "old-session" ,
1065+ initialized : true
1066+ // No initializeParams - simulating old storage format
1067+ } ;
1068+
1069+ const mockStorage = {
1070+ get : async ( ) => oldState ,
1071+ set : async ( ) => { }
1072+ } ;
1073+
1074+ const server = createTestServer ( ) ;
1075+ const transport = await setupTransport ( server , {
1076+ storage : mockStorage ,
1077+ enableJsonResponse : true
1078+ } ) ;
1079+
1080+ const request = new Request ( "http://example.com/" , {
1081+ method : "POST" ,
1082+ headers : {
1083+ "Content-Type" : "application/json" ,
1084+ Accept : "application/json, text/event-stream" ,
1085+ "mcp-session-id" : "old-session"
1086+ } ,
1087+ body : JSON . stringify ( {
1088+ jsonrpc : "2.0" ,
1089+ id : "1" ,
1090+ method : "tools/list" ,
1091+ params : { }
1092+ } )
1093+ } ) ;
1094+
1095+ // Should not throw
1096+ const response = await transport . handleRequest ( request ) ;
1097+ expect ( response . status ) . toBe ( 200 ) ;
1098+
1099+ // Session restored but capabilities not available (no initializeParams)
1100+ expect ( transport . sessionId ) . toBe ( "old-session" ) ;
1101+ expect ( server . server . getClientCapabilities ( ) ) . toBeUndefined ( ) ;
1102+ } ) ;
1103+
1104+ it ( "should persist initializeParams with empty capabilities" , async ( ) => {
1105+ const server = createTestServer ( ) ;
1106+ let storedState : TransportState | undefined ;
1107+
1108+ const mockStorage = {
1109+ get : async ( ) => storedState ,
1110+ set : async ( state : TransportState ) => {
1111+ storedState = state ;
1112+ }
1113+ } ;
1114+
1115+ const transport = await setupTransport ( server , {
1116+ sessionIdGenerator : ( ) => "test-session" ,
1117+ storage : mockStorage ,
1118+ enableJsonResponse : true
1119+ } ) ;
1120+
1121+ const request = new Request ( "http://example.com/" , {
1122+ method : "POST" ,
1123+ headers : {
1124+ "Content-Type" : "application/json" ,
1125+ Accept : "application/json, text/event-stream"
1126+ } ,
1127+ body : JSON . stringify ( {
1128+ jsonrpc : "2.0" ,
1129+ id : "1" ,
1130+ method : "initialize" ,
1131+ params : {
1132+ capabilities : { } , // Empty but present
1133+ clientInfo : { name : "test-client" , version : "1.0" } ,
1134+ protocolVersion : "2025-06-18"
1135+ }
1136+ } )
1137+ } ) ;
1138+
1139+ const response = await transport . handleRequest ( request ) ;
1140+ await response . json ( ) ;
1141+
1142+ expect ( response . status ) . toBe ( 200 ) ;
1143+ expect ( storedState ?. initializeParams ) . toBeDefined ( ) ;
1144+ expect ( storedState ?. initializeParams ?. capabilities ) . toEqual ( { } ) ;
1145+ } ) ;
1146+ } ) ;
1147+
8611148 describe ( "Session Management" , ( ) => {
8621149 it ( "should use custom sessionIdGenerator" , async ( ) => {
8631150 const server = createTestServer ( ) ;
0 commit comments