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