From 9d118d60ce0b92d05bb2895fb89ab08430488814 Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 26 May 2026 19:39:34 +0000 Subject: [PATCH] feat: wire MCPAppRenderer into ChatPanel for MCP App tool results --- frontend/src/components/ChatPanel.tsx | 17 +++++-- frontend/src/test/ChatPanel.test.tsx | 73 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ChatPanel.tsx b/frontend/src/components/ChatPanel.tsx index 53d7a98..d44d597 100644 --- a/frontend/src/components/ChatPanel.tsx +++ b/frontend/src/components/ChatPanel.tsx @@ -3,6 +3,7 @@ import type { KeyboardEvent } from 'react' import ReactMarkdown from 'react-markdown' import { IconSend } from '@tabler/icons-react' import { ToolCallCard } from './ToolCallCard' +import { MCPAppRenderer } from './MCPAppRenderer' import type { ChatMessage } from '../hooks/useChat' interface ChatPanelProps { @@ -61,9 +62,19 @@ export function ChatPanel({ messages, isStreaming, onSend, sessionId }: ChatPane {message.role === 'assistant' ? (
{message.content} - {message.toolCalls?.map(tc => ( - - ))} + {message.toolCalls?.map(tc => + tc._meta?.ui?.resourceUri ? ( + onSend(content)} + /> + ) : ( + + ) + )}
) : (
{message.content}
diff --git a/frontend/src/test/ChatPanel.test.tsx b/frontend/src/test/ChatPanel.test.tsx index 4b434a1..7f23bc1 100644 --- a/frontend/src/test/ChatPanel.test.tsx +++ b/frontend/src/test/ChatPanel.test.tsx @@ -14,6 +14,13 @@ vi.mock('react-markdown', () => ({ default: ({ children }: { children: string }) =>
{children}
, })) +// Mock MCPAppRenderer to avoid fetch/iframe in test environment +vi.mock('../components/MCPAppRenderer', () => ({ + MCPAppRenderer: ({ resourceUri }: { resourceUri: string }) => ( +
+ ), +})) + describe('ChatPanel', () => { const noop = () => undefined @@ -90,6 +97,72 @@ describe('ChatPanel', () => { expect(container.querySelector('.tool-call-card')).toBeTruthy() }) + it('renders MCPAppRenderer for tool calls with _meta.ui.resourceUri', () => { + const messages: ChatMessage[] = [ + { + id: '1', + role: 'assistant', + content: 'Running an MCP app', + toolCalls: [ + { + id: 'tc-mcp', + name: 'mcp_app', + args: {}, + status: 'complete', + _meta: { ui: { resourceUri: 'app://chart' } }, + structuredContent: { value: 42 }, + }, + ], + }, + ] + render( + + ) + expect(screen.getByTestId('mcp-app-renderer')).toBeTruthy() + }) + + it('does not render ToolCallCard for tool calls with _meta.ui.resourceUri', () => { + const messages: ChatMessage[] = [ + { + id: '1', + role: 'assistant', + content: 'Running an MCP app', + toolCalls: [ + { + id: 'tc-mcp', + name: 'mcp_app', + args: {}, + status: 'complete', + _meta: { ui: { resourceUri: 'app://chart' } }, + structuredContent: { value: 42 }, + }, + ], + }, + ] + const { container } = render( + + ) + expect(container.querySelector('.tool-call-card')).toBeNull() + }) + + it('renders ToolCallCard for tool calls without _meta.ui.resourceUri', () => { + const messages: ChatMessage[] = [ + { + id: '1', + role: 'assistant', + content: 'Running a plain tool', + toolCalls: [ + { id: 'tc-plain', name: 'bash', args: {}, status: 'complete' }, + ], + }, + ] + const { container } = render( + + ) + expect(container.querySelector('.tool-call-card')).toBeTruthy() + expect(screen.queryByTestId('mcp-app-renderer')).toBeNull() + }) + it('shows Researching... when isStreaming and messages exist', () => { const messages: ChatMessage[] = [ { id: '1', role: 'user', content: 'Question' },