@@ -768,4 +768,175 @@ describe.each(zodTestMatrix)('$zodVersionLabel', (entry: ZodMatrixEntry) => {
768768 await expect ( transport . start ( ) ) . rejects . toThrow ( 'Transport already started' ) ;
769769 } ) ;
770770 } ) ;
771+
772+ describe ( 'HTTPServerTransport - onerror callback' , ( ) => {
773+ let transport : WebStandardStreamableHTTPServerTransport ;
774+ let mcpServer : McpServer ;
775+ let errors : Error [ ] ;
776+
777+ beforeEach ( async ( ) => {
778+ errors = [ ] ;
779+ mcpServer = new McpServer ( { name : 'test-server' , version : '1.0.0' } , { capabilities : { } } ) ;
780+
781+ transport = new WebStandardStreamableHTTPServerTransport ( {
782+ sessionIdGenerator : ( ) => randomUUID ( )
783+ } ) ;
784+
785+ transport . onerror = err => errors . push ( err ) ;
786+
787+ await mcpServer . connect ( transport ) ;
788+ } ) ;
789+
790+ afterEach ( async ( ) => {
791+ await transport . close ( ) ;
792+ } ) ;
793+
794+ async function initializeServer ( ) : Promise < string > {
795+ const request = createRequest ( 'POST' , TEST_MESSAGES . initialize ) ;
796+ const response = await transport . handleRequest ( request ) ;
797+ return response . headers . get ( 'mcp-session-id' ) as string ;
798+ }
799+
800+ it ( 'should call onerror for invalid JSON' , async ( ) => {
801+ const request = new Request ( 'http://localhost/mcp' , {
802+ method : 'POST' ,
803+ headers : {
804+ Accept : 'application/json, text/event-stream' ,
805+ 'Content-Type' : 'application/json'
806+ } ,
807+ body : 'not valid json'
808+ } ) ;
809+
810+ const response = await transport . handleRequest ( request ) ;
811+
812+ expect ( response . status ) . toBe ( 400 ) ;
813+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
814+ expect ( errors [ 0 ] . message ) . toContain ( 'Parse error' ) ;
815+ } ) ;
816+
817+ it ( 'should call onerror for invalid JSON-RPC message' , async ( ) => {
818+ const request = new Request ( 'http://localhost/mcp' , {
819+ method : 'POST' ,
820+ headers : {
821+ Accept : 'application/json, text/event-stream' ,
822+ 'Content-Type' : 'application/json'
823+ } ,
824+ body : JSON . stringify ( { not : 'valid jsonrpc' } )
825+ } ) ;
826+
827+ const response = await transport . handleRequest ( request ) ;
828+
829+ expect ( response . status ) . toBe ( 400 ) ;
830+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
831+ expect ( errors [ 0 ] . message ) . toContain ( 'Parse error' ) ;
832+ } ) ;
833+
834+ it ( 'should call onerror for missing Accept header on POST' , async ( ) => {
835+ const request = createRequest ( 'POST' , TEST_MESSAGES . initialize , { accept : 'application/json' } ) ;
836+
837+ const response = await transport . handleRequest ( request ) ;
838+
839+ expect ( response . status ) . toBe ( 406 ) ;
840+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
841+ expect ( errors [ 0 ] . message ) . toContain ( 'Not Acceptable' ) ;
842+ } ) ;
843+
844+ it ( 'should call onerror for unsupported Content-Type' , async ( ) => {
845+ const request = new Request ( 'http://localhost/mcp' , {
846+ method : 'POST' ,
847+ headers : {
848+ Accept : 'application/json, text/event-stream' ,
849+ 'Content-Type' : 'text/plain'
850+ } ,
851+ body : JSON . stringify ( TEST_MESSAGES . initialize )
852+ } ) ;
853+
854+ const response = await transport . handleRequest ( request ) ;
855+
856+ expect ( response . status ) . toBe ( 415 ) ;
857+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
858+ expect ( errors [ 0 ] . message ) . toContain ( 'Unsupported Media Type' ) ;
859+ } ) ;
860+
861+ it ( 'should call onerror for server not initialized' , async ( ) => {
862+ const request = createRequest ( 'POST' , TEST_MESSAGES . toolsList ) ;
863+
864+ const response = await transport . handleRequest ( request ) ;
865+
866+ expect ( response . status ) . toBe ( 400 ) ;
867+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
868+ expect ( errors [ 0 ] . message ) . toContain ( 'Server not initialized' ) ;
869+ } ) ;
870+
871+ it ( 'should call onerror for invalid session ID' , async ( ) => {
872+ await initializeServer ( ) ;
873+
874+ const request = createRequest ( 'POST' , TEST_MESSAGES . toolsList , { sessionId : 'invalid-session-id' } ) ;
875+
876+ const response = await transport . handleRequest ( request ) ;
877+
878+ expect ( response . status ) . toBe ( 404 ) ;
879+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
880+ expect ( errors [ 0 ] . message ) . toContain ( 'Session not found' ) ;
881+ } ) ;
882+
883+ it ( 'should call onerror for re-initialization attempt' , async ( ) => {
884+ await initializeServer ( ) ;
885+
886+ const request = createRequest ( 'POST' , TEST_MESSAGES . initialize ) ;
887+
888+ const response = await transport . handleRequest ( request ) ;
889+
890+ expect ( response . status ) . toBe ( 400 ) ;
891+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
892+ expect ( errors [ 0 ] . message ) . toContain ( 'Server already initialized' ) ;
893+ } ) ;
894+
895+ it ( 'should call onerror for GET without Accept header' , async ( ) => {
896+ const sessionId = await initializeServer ( ) ;
897+
898+ const request = createRequest ( 'GET' , undefined , { sessionId, accept : 'application/json' } ) ;
899+
900+ const response = await transport . handleRequest ( request ) ;
901+
902+ expect ( response . status ) . toBe ( 406 ) ;
903+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
904+ expect ( errors [ 0 ] . message ) . toContain ( 'Not Acceptable' ) ;
905+ } ) ;
906+
907+ it ( 'should call onerror for concurrent SSE streams' , async ( ) => {
908+ const sessionId = await initializeServer ( ) ;
909+
910+ const request1 = createRequest ( 'GET' , undefined , { sessionId } ) ;
911+ await transport . handleRequest ( request1 ) ;
912+
913+ const request2 = createRequest ( 'GET' , undefined , { sessionId } ) ;
914+ const response2 = await transport . handleRequest ( request2 ) ;
915+
916+ expect ( response2 . status ) . toBe ( 409 ) ;
917+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
918+ expect ( errors [ 0 ] . message ) . toContain ( 'Conflict' ) ;
919+ } ) ;
920+
921+ it ( 'should call onerror for unsupported protocol version' , async ( ) => {
922+ const sessionId = await initializeServer ( ) ;
923+
924+ const request = new Request ( 'http://localhost/mcp' , {
925+ method : 'POST' ,
926+ headers : {
927+ 'Content-Type' : 'application/json' ,
928+ Accept : 'application/json, text/event-stream' ,
929+ 'mcp-session-id' : sessionId ,
930+ 'mcp-protocol-version' : 'unsupported-version'
931+ } ,
932+ body : JSON . stringify ( TEST_MESSAGES . toolsList )
933+ } ) ;
934+
935+ const response = await transport . handleRequest ( request ) ;
936+
937+ expect ( response . status ) . toBe ( 400 ) ;
938+ expect ( errors . length ) . toBeGreaterThan ( 0 ) ;
939+ expect ( errors [ 0 ] . message ) . toContain ( 'Unsupported protocol version' ) ;
940+ } ) ;
941+ } ) ;
771942} ) ;
0 commit comments