diff --git a/apps/web/src/components/gen-ui/action-step.tsx b/apps/web/src/components/gen-ui/action-step.tsx index 63d8adc5..54354f76 100644 --- a/apps/web/src/components/gen-ui/action-step.tsx +++ b/apps/web/src/components/gen-ui/action-step.tsx @@ -12,6 +12,7 @@ import { XCircle, Loader2, Globe, + Zap, FileCode, CloudDownload, } from "lucide-react"; @@ -101,6 +102,13 @@ type SearchActionProps = BaseActionProps & errorCode?: number; }; +type McpActionProps = BaseActionProps & { + actionType: "mcp"; + toolName: string; + args: Record; + output?: string; +}; + export type ActionItemProps = | (BaseActionProps & { status: "loading" }) | ShellActionProps @@ -108,6 +116,7 @@ export type ActionItemProps = | InstallDependenciesActionProps | PlannerNotesActionProps | GetURLContentActionProps + | McpActionProps | SearchActionProps; export type ActionStepProps = { @@ -200,6 +209,10 @@ function ActionItem(props: ActionItemProps) { : "Failed to fetch URL content"; } else if (props.actionType === "search") { return props.success ? "Search completed" : "Search failed"; + } else if (props.actionType === "mcp") { + return props.success + ? `${props.toolName} completed` + : `${props.toolName} failed`; } } @@ -221,6 +234,10 @@ function ActionItem(props: ActionItemProps) { return !!props.diff; } else if (props.actionType === "planner_notes") { return !!(props.notes && props.notes.length > 0); + } else if (props.actionType === "mcp") { + const hasArgs = props.args && Object.keys(props.args).length > 0; + const hasOutput = !!props.output; + return hasArgs || hasOutput; } return false; @@ -267,6 +284,13 @@ function ActionItem(props: ActionItemProps) { icon={} /> ); + } else if (props.actionType === "mcp") { + return ( + } + /> + ); } else if (props.actionType === "search") { return ( ); + } else if (props.actionType === "mcp") { + return ( +
+ + {props.toolName} + +
+ ); } else { return ( @@ -423,6 +455,59 @@ function ActionItem(props: ActionItemProps) { )} ); + } else if (props.actionType === "mcp") { + const hasArgs = props.args && Object.keys(props.args).length > 0; + const hasOutput = !!props.output; + + if (!hasArgs && !hasOutput) { + return null; + } + + return ( +
+ {hasArgs && ( +
+
+ Arguments +
+
+ {Object.entries(props.args).map(([key, value]) => ( +
+
+ {key} +
+
+ {typeof value === "object" ? ( + + {JSON.stringify(value, null, 2)} + + ) : ( + + {String(value)} + + )} +
+
+ ))} +
+
+ )} + + {hasOutput && ( + <> +
+ Output +
+
+                {props.output}
+              
+ + )} +
+ ); } else if (props.actionType === "get_url_content" && props.output) { return (
diff --git a/apps/web/src/components/thread/messages/ai.tsx b/apps/web/src/components/thread/messages/ai.tsx index 253d5df6..4e48aa1d 100644 --- a/apps/web/src/components/thread/messages/ai.tsx +++ b/apps/web/src/components/thread/messages/ai.tsx @@ -104,6 +104,20 @@ type ConversationHistorySummaryToolArgs = z.infer< typeof conversationHistorySummaryTool.schema >; +// Helper function to detect MCP tools by checking if tool name is NOT in known tools +function isMcpTool(toolName: string): boolean { + const knownToolNames = [ + shellTool.name, + applyPatchTool.name, + installDependenciesTool.name, + plannerNotesTool.name, + getURLContentTool.name, + openPrTool.name, + diagnoseErrorTool.name, + ]; + return !knownToolNames.some((t) => t === toolName); +} + function CustomComponent({ message, thread, @@ -247,6 +261,16 @@ export function mapToolMessageToActionStepProps( output: getContentString(message.content), reasoningText, }; + } else if (toolCall && isMcpTool(toolCall.name)) { + return { + actionType: "mcp", + status, + success, + toolName: toolCall.name, + args: toolCall.args as Record, + output: getContentString(message.content), + reasoningText, + }; } return { status: "loading", @@ -314,7 +338,8 @@ export function AssistantMessage({ tc.name === searchTool.name || tc.name === installDependenciesTool.name || tc.name === plannerNotesTool.name || - tc.name === getURLContentTool.name, + tc.name === getURLContentTool.name || + isMcpTool(tc.name), ) : []; @@ -613,7 +638,14 @@ export function AssistantMessage({ output: "", } as ActionItemProps; } else { - if (isShellTool) { + if (isMcpTool(toolCall.name)) { + return { + actionType: "mcp", + status: "generating", + toolName: toolCall.name, + args: toolCall.args as Record, + } as ActionItemProps; + } else if (isShellTool) { const args = toolCall.args as ShellToolArgs; return { actionType: "shell",