44 type UIActionResult ,
55 isUIResource ,
66} from '@mcp-ui/client'
7- import { useState , useRef , useEffect , useCallback } from 'react'
7+ import { useState , useRef , useEffect , useCallback , type RefObject } from 'react'
88import { Form , isRouteErrorResponse } from 'react-router'
99import { type Route } from './+types/mcp-ui-renderer'
1010
@@ -83,7 +83,7 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
8383 const [ messages , setMessages ] = useState <
8484 Array < {
8585 id : string
86- type : 'sent' | 'received' | 'response'
86+ type : 'sent' | 'received' | 'response' | 'internal'
8787 content : string
8888 timestamp : Date
8989 messageId ?: string
@@ -103,6 +103,7 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
103103 >
104104 > ( new Map ( ) )
105105 const messageInputRef = useRef < HTMLTextAreaElement > ( null )
106+ const iframeRef = useRef < HTMLIFrameElement > ( null )
106107 const [ isErrorResponse , setIsErrorResponse ] = useState < boolean > ( false )
107108
108109 // Auto-scroll when new messages are added and user is at bottom
@@ -112,8 +113,49 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
112113 }
113114 } , [ messages , isAtBottom , scrollToBottom ] )
114115
116+ // Listen to all iframe messages
117+ useEffect ( ( ) => {
118+ const handleMessage = ( event : MessageEvent ) => {
119+ // Only handle messages from our iframe
120+ if (
121+ iframeRef . current &&
122+ event . source === iframeRef . current . contentWindow
123+ ) {
124+ try {
125+ const messageData =
126+ typeof event . data === 'string' ? JSON . parse ( event . data ) : event . data
127+
128+ // Check if this is a UI action message that would be handled by onUIAction
129+ const isUIActionMessage =
130+ messageData &&
131+ typeof messageData === 'object' &&
132+ messageData . type &&
133+ [ 'tool' , 'prompt' , 'link' , 'intent' , 'notify' ] . includes (
134+ messageData . type ,
135+ )
136+
137+ // If it's not a UI action message, or if it's a different type of message, display it as internal
138+ if ( ! isUIActionMessage ) {
139+ const messageContent = JSON . stringify ( messageData , null , 2 )
140+ addMessage ( 'internal' , messageContent , messageData . messageId )
141+ }
142+ } catch {
143+ // If we can't parse the message, still display it as internal
144+ const messageContent =
145+ typeof event . data === 'string'
146+ ? event . data
147+ : JSON . stringify ( event . data , null , 2 )
148+ addMessage ( 'internal' , messageContent )
149+ }
150+ }
151+ }
152+
153+ window . addEventListener ( 'message' , handleMessage )
154+ return ( ) => window . removeEventListener ( 'message' , handleMessage )
155+ } , [ ] )
156+
115157 const addMessage = (
116- type : 'sent' | 'received' | 'response' ,
158+ type : 'sent' | 'received' | 'response' | 'internal' ,
117159 content : string ,
118160 messageId ?: string ,
119161 respondsTo ?: string ,
@@ -138,7 +180,6 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
138180 const messageId = 'messageId' in result ? result . messageId : undefined
139181
140182 const fullResult = JSON . stringify ( result , null , 2 )
141- console . log ( 'Full UIActionResult:' , fullResult )
142183 addMessage ( 'received' , fullResult , messageId )
143184
144185 // Return a promise that will be resolved when user submits response
@@ -160,11 +201,18 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
160201 selectedMessageId &&
161202 pendingPromisesRef . current . has ( selectedMessageId )
162203 ) {
163- const promise = pendingPromisesRef . current . get ( selectedMessageId ) !
204+ const promise = pendingPromisesRef . current . get ( selectedMessageId )
205+ if ( ! promise ) return
206+
164207 if ( isErrorResponse ) {
165208 promise . reject ( message )
166209 } else {
167- promise . resolve ( message )
210+ try {
211+ const parsed = JSON . parse ( message )
212+ promise . resolve ( parsed )
213+ } catch {
214+ promise . resolve ( message )
215+ }
168216 }
169217 pendingPromisesRef . current . delete ( selectedMessageId )
170218 }
@@ -226,10 +274,10 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
226274 border : 'none' ,
227275 borderRadius : '0.5rem' ,
228276 } ,
229- iframeRenderData : {
230- theme : 'light' ,
231- userId : 'demo-user' ,
277+ iframeProps : {
278+ ref : iframeRef as RefObject < HTMLIFrameElement > ,
232279 } ,
280+ autoResizeIframe : true ,
233281 } }
234282 />
235283 </ div >
@@ -254,7 +302,7 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
254302 </ div >
255303
256304 { /* Messages Area */ }
257- < div className = "flex max-h-[680px] flex-1 flex-col" >
305+ < div className = "flex flex-1 flex-col" >
258306 < div className = "flex-1 space-y-3 overflow-x-hidden overflow-y-auto p-4" >
259307 { messages . length === 0 ? (
260308 < div className = "flex h-full items-center justify-center" >
@@ -278,7 +326,9 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
278326 ? 'bg-blue-600 text-white'
279327 : message . type === 'received'
280328 ? 'bg-gray-100 text-gray-900 dark:bg-gray-700 dark:text-gray-100'
281- : 'bg-yellow-100 text-yellow-900 dark:bg-yellow-900/20 dark:text-yellow-100'
329+ : message . type === 'internal'
330+ ? 'bg-purple-100 text-purple-900 dark:bg-purple-900/20 dark:text-purple-100'
331+ : 'bg-yellow-100 text-yellow-900 dark:bg-yellow-900/20 dark:text-yellow-100'
282332 } `}
283333 >
284334 < div className = "mb-1 flex items-center justify-between gap-1" >
@@ -289,7 +339,9 @@ export default function MCPRenderer({ loaderData }: Route.ComponentProps) {
289339 ? 'text-blue-100'
290340 : message . type === 'received'
291341 ? 'text-gray-500 dark:text-gray-400'
292- : 'text-yellow-600 dark:text-yellow-400'
342+ : message . type === 'internal'
343+ ? 'text-purple-600 dark:text-purple-400'
344+ : 'text-yellow-600 dark:text-yellow-400'
293345 } `}
294346 >
295347 < MessageTypeLabel
@@ -434,6 +486,7 @@ const ACTION_EMOJIS = {
434486 link : '🔗' ,
435487 intent : '🎯' ,
436488 notify : '🔔' ,
489+ internal : '📨' ,
437490} as const
438491
439492function MessageTypeLabel ( {
@@ -466,6 +519,15 @@ function MessageTypeLabel({
466519 }
467520 }
468521
522+ if ( type === 'internal' ) {
523+ return (
524+ < >
525+ 📨 INTERNAL
526+ { messageId && ` (${ messageId . slice ( 0 , 8 ) } )` }
527+ </ >
528+ )
529+ }
530+
469531 return (
470532 < >
471533 { type . toUpperCase ( ) }
0 commit comments