@@ -53,7 +53,7 @@ export const McpExecution = ({
5353 const [ isResponseExpanded , setIsResponseExpanded ] = useState ( false )
5454
5555 // Try to parse JSON and return both the result and formatted text
56- const tryParseJson = ( text : string ) : { isJson : boolean ; formatted : string } => {
56+ const tryParseJson = useCallback ( ( text : string ) : { isJson : boolean ; formatted : string } => {
5757 if ( ! text ) return { isJson : false , formatted : "" }
5858
5959 try {
@@ -68,10 +68,51 @@ export const McpExecution = ({
6868 formatted : text ,
6969 }
7070 }
71- }
71+ } , [ ] )
72+
73+ // Only parse response data when expanded AND complete to avoid parsing partial JSON
74+ const responseData = useMemo ( ( ) => {
75+ if ( ! isResponseExpanded ) {
76+ return { isJson : false , formatted : responseText }
77+ }
78+ // Only try to parse JSON if the response is complete
79+ if ( status && status . status === "completed" ) {
80+ return tryParseJson ( responseText )
81+ }
82+ // For partial responses, just return as-is without parsing
83+ return { isJson : false , formatted : responseText }
84+ } , [ responseText , isResponseExpanded , tryParseJson , status ] )
85+
86+ // Only parse arguments data when complete to avoid parsing partial JSON
87+ const argumentsData = useMemo ( ( ) => {
88+ if ( ! argumentsText ) {
89+ return { isJson : false , formatted : "" }
90+ }
91+
92+ // For arguments, we don't have a streaming status, so we check if it looks like complete JSON
93+ const trimmed = argumentsText . trim ( )
94+
95+ // Basic check for complete JSON structure
96+ if (
97+ trimmed &&
98+ ( ( trimmed . startsWith ( "{" ) && trimmed . endsWith ( "}" ) ) || ( trimmed . startsWith ( "[" ) && trimmed . endsWith ( "]" ) ) )
99+ ) {
100+ // Try to parse, but if it fails, return as-is
101+ try {
102+ const parsed = JSON . parse ( trimmed )
103+ return {
104+ isJson : true ,
105+ formatted : JSON . stringify ( parsed , null , 2 ) ,
106+ }
107+ } catch {
108+ // JSON structure looks complete but is invalid, return as-is
109+ return { isJson : false , formatted : argumentsText }
110+ }
111+ }
72112
73- const responseData = useMemo ( ( ) => tryParseJson ( responseText ) , [ responseText ] )
74- const argumentsData = useMemo ( ( ) => tryParseJson ( argumentsText ) , [ argumentsText ] )
113+ // For non-JSON or incomplete data, just return as-is
114+ return { isJson : false , formatted : argumentsText }
115+ } , [ argumentsText ] )
75116
76117 const formattedResponseText = responseData . formatted
77118 const formattedArgumentsText = argumentsData . formatted
@@ -99,16 +140,8 @@ export const McpExecution = ({
99140
100141 if ( data . status === "output" && data . response ) {
101142 setResponseText ( ( prev ) => prev + data . response )
102- // Keep the arguments when we get output
103- if ( isArguments && argumentsText === responseText ) {
104- setArgumentsText ( responseText )
105- }
106143 } else if ( data . status === "completed" && data . response ) {
107144 setResponseText ( data . response )
108- // Keep the arguments when we get completed response
109- if ( isArguments && argumentsText === responseText ) {
110- setArgumentsText ( responseText )
111- }
112145 }
113146 }
114147 }
@@ -117,30 +150,16 @@ export const McpExecution = ({
117150 }
118151 }
119152 } ,
120- [ argumentsText , executionId , isArguments , responseText ] ,
153+ [ executionId ] ,
121154 )
122155
123156 useEvent ( "message" , onMessage )
124157
125158 // Initialize with text if provided and parse command/response sections
126159 useEffect ( ( ) => {
127- // Handle arguments text
160+ // Handle arguments text - don't parse JSON here as it might be incomplete
128161 if ( text ) {
129- try {
130- // Try to parse the text as JSON for arguments
131- const jsonObj = safeJsonParse < any > ( text , null )
132-
133- if ( jsonObj && typeof jsonObj === "object" ) {
134- // Format the JSON for display
135- setArgumentsText ( JSON . stringify ( jsonObj , null , 2 ) )
136- } else {
137- // If not valid JSON, use as is
138- setArgumentsText ( text )
139- }
140- } catch ( _e ) {
141- // If parsing fails, use text as is
142- setArgumentsText ( text )
143- }
162+ setArgumentsText ( text )
144163 }
145164
146165 // Handle response text
@@ -258,6 +277,7 @@ export const McpExecution = ({
258277 response = { formattedResponseText }
259278 isJson = { responseIsJson }
260279 hasArguments = { ! ! ( isArguments || useMcpServer ?. arguments || argumentsText ) }
280+ isPartial = { status ? status . status !== "completed" : false }
261281 />
262282 </ div >
263283 </ >
@@ -271,21 +291,38 @@ const ResponseContainerInternal = ({
271291 response,
272292 isJson,
273293 hasArguments,
294+ isPartial = false ,
274295} : {
275296 isExpanded : boolean
276297 response : string
277298 isJson : boolean
278299 hasArguments ?: boolean
279- } ) => (
280- < div
281- className = { cn ( "overflow-hidden" , {
282- "max-h-0" : ! isExpanded ,
283- "max-h-[100%] mt-1 pt-1 border-t border-border/25" : isExpanded && hasArguments ,
284- "max-h-[100%] mt-1 pt-1" : isExpanded && ! hasArguments ,
285- } ) } >
286- { response . length > 0 &&
287- ( isJson ? < CodeBlock source = { response } language = "json" /> : < Markdown markdown = { response } partial = { false } /> ) }
288- </ div >
289- )
300+ isPartial ?: boolean
301+ } ) => {
302+ // Only render content when expanded to prevent performance issues with large responses
303+ if ( ! isExpanded || response . length === 0 ) {
304+ return (
305+ < div
306+ className = { cn ( "overflow-hidden" , {
307+ "max-h-0" : ! isExpanded ,
308+ } ) }
309+ />
310+ )
311+ }
312+
313+ return (
314+ < div
315+ className = { cn ( "overflow-hidden" , {
316+ "max-h-[100%] mt-1 pt-1 border-t border-border/25" : hasArguments ,
317+ "max-h-[100%] mt-1 pt-1" : ! hasArguments ,
318+ } ) } >
319+ { isJson ? (
320+ < CodeBlock source = { response } language = "json" />
321+ ) : (
322+ < Markdown markdown = { response } partial = { isPartial } />
323+ ) }
324+ </ div >
325+ )
326+ }
290327
291328const ResponseContainer = memo ( ResponseContainerInternal )
0 commit comments