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