Skip to content

Commit 999a167

Browse files
committed
feat: enhance MCPRenderer to handle internal messages and improve iframe communication
1 parent 9a311e2 commit 999a167

File tree

1 file changed

+74
-12
lines changed

1 file changed

+74
-12
lines changed

epicshop/epic-me/app/routes/mcp-ui-renderer.tsx

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
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'
88
import { Form, isRouteErrorResponse } from 'react-router'
99
import { 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

439492
function 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

Comments
 (0)