@@ -560,6 +560,143 @@ data: {"event": "token", "data": {"id": 2, "token": ""}}\n
560560 expect ( onComplete ) . toHaveBeenCalledWith ( 'Hi from conversation 1!' ) ;
561561 } ) ;
562562
563+ it ( 'should parse MCP-style tool_call and tool_result (name/args and content)' , async ( ) => {
564+ const toolCallId = 'mcp_list_67d3a067-e262-4bef-b467-6c82f622bd4a' ;
565+ const mcpStream = createSSEStream ( [
566+ {
567+ event : 'start' ,
568+ data : { conversation_id : 'conv-mcp' } ,
569+ } ,
570+ {
571+ event : 'tool_call' ,
572+ data : {
573+ id : toolCallId ,
574+ name : 'mcp_list_tools' ,
575+ args : { server_label : 'mcp-integration-tools' } ,
576+ type : 'mcp_list_tools' ,
577+ } ,
578+ } ,
579+ {
580+ event : 'tool_result' ,
581+ data : {
582+ id : toolCallId ,
583+ status : 'success' ,
584+ content : '{"server_label":"mcp-integration-tools","tools":[]}' ,
585+ } ,
586+ } ,
587+ {
588+ event : 'token' ,
589+ data : { id : 0 , token : 'Done.' } ,
590+ } ,
591+ ] ) ;
592+
593+ const mockApi = {
594+ createMessage : jest . fn ( ) . mockResolvedValue ( {
595+ read : jest
596+ . fn ( )
597+ . mockResolvedValueOnce ( {
598+ done : false ,
599+ value : new TextEncoder ( ) . encode ( mcpStream ) ,
600+ } )
601+ . mockResolvedValueOnce ( { done : true , value : null } ) ,
602+ } ) ,
603+ } ;
604+ ( useApi as jest . Mock ) . mockReturnValue ( mockApi ) ;
605+
606+ const { result } = renderHook (
607+ ( ) =>
608+ useConversationMessages (
609+ 'conv-mcp' ,
610+ 'test-user' ,
611+ 'gpt-4' ,
612+ 'openai' ,
613+ 'user.png' ,
614+ ) ,
615+ { wrapper } ,
616+ ) ;
617+
618+ await act ( async ( ) => {
619+ await result . current . handleInputPrompt ( 'List MCP tools' ) ;
620+ } ) ;
621+
622+ await waitFor ( ( ) => {
623+ const msgs = result . current . conversations [ 'conv-mcp' ] ;
624+ const bot = msgs ?. [ 1 ] ;
625+ expect ( bot ?. toolCalls ) . toHaveLength ( 1 ) ;
626+ expect ( bot ?. toolCalls ?. [ 0 ] ) . toEqual (
627+ expect . objectContaining ( {
628+ id : toolCallId ,
629+ toolName : 'mcp_list_tools' ,
630+ arguments : { server_label : 'mcp-integration-tools' } ,
631+ response : '{"server_label":"mcp-integration-tools","tools":[]}' ,
632+ isLoading : false ,
633+ } ) ,
634+ ) ;
635+ expect ( bot ?. content ) . toContain ( 'Done.' ) ;
636+ } ) ;
637+ } ) ;
638+
639+ it ( 'should complete legacy tool_result when response field is omitted' , async ( ) => {
640+ const legacyStream = createSSEStream ( [
641+ {
642+ event : 'start' ,
643+ data : { conversation_id : 'conv-legacy-res' } ,
644+ } ,
645+ {
646+ event : 'tool_call' ,
647+ data : {
648+ id : 1 ,
649+ token : { tool_name : 'fetch-techdocs' , arguments : { owner : 'a' } } ,
650+ } ,
651+ } ,
652+ {
653+ event : 'tool_result' ,
654+ data : { id : 1 , token : { tool_name : 'fetch-techdocs' } } ,
655+ } ,
656+ ] ) ;
657+
658+ const mockApi = {
659+ createMessage : jest . fn ( ) . mockResolvedValue ( {
660+ read : jest
661+ . fn ( )
662+ . mockResolvedValueOnce ( {
663+ done : false ,
664+ value : new TextEncoder ( ) . encode ( legacyStream ) ,
665+ } )
666+ . mockResolvedValueOnce ( { done : true , value : null } ) ,
667+ } ) ,
668+ } ;
669+ ( useApi as jest . Mock ) . mockReturnValue ( mockApi ) ;
670+
671+ const { result } = renderHook (
672+ ( ) =>
673+ useConversationMessages (
674+ 'conv-legacy-res' ,
675+ 'test-user' ,
676+ 'gpt-4' ,
677+ 'openai' ,
678+ 'user.png' ,
679+ ) ,
680+ { wrapper } ,
681+ ) ;
682+
683+ await act ( async ( ) => {
684+ await result . current . handleInputPrompt ( 'techdocs' ) ;
685+ } ) ;
686+
687+ await waitFor ( ( ) => {
688+ const msgs = result . current . conversations [ 'conv-legacy-res' ] ;
689+ const bot = msgs ?. [ 1 ] ;
690+ expect ( bot ?. toolCalls ?. [ 0 ] ) . toEqual (
691+ expect . objectContaining ( {
692+ toolName : 'fetch-techdocs' ,
693+ response : '' ,
694+ isLoading : false ,
695+ } ) ,
696+ ) ;
697+ } ) ;
698+ } ) ;
699+
563700 it ( 'should resume streaming for the first conversation after switching back and complete' , async ( ) => {
564701 const onComplete = jest . fn ( ) ;
565702
0 commit comments