From 651a7d27fc82625d5087f74ebbd8fb6d43fbd410 Mon Sep 17 00:00:00 2001 From: David Kaya Date: Sat, 28 Mar 2026 22:08:25 +0100 Subject: [PATCH] feat: show MCP probe progress in approval pill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Thread mcpProbingServerIds from workspace state through App → ChatPane → InlineApprovalPill - Show animated spinner and 'probing…' label on pill button while any MCP server is being probed - Show per-server probing indicator in popover: spinning icon, 'probing…' badge, hidden toggle - Render approval pill during probing even when no MCP tools are known yet Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/renderer/App.tsx | 1 + src/renderer/components/ChatPane.tsx | 10 +++- src/renderer/components/chat/InlinePills.tsx | 56 +++++++++++++++----- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index e49f3f3..c021aa6 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -333,6 +333,7 @@ export default function App() { }); }} availableModels={availableModels} + mcpProbingServerIds={workspace.mcpProbingServerIds} pattern={patternForSession} project={projectForSession} runtimeTools={sidecarCapabilities?.runtimeTools} diff --git a/src/renderer/components/ChatPane.tsx b/src/renderer/components/ChatPane.tsx index 7d158b8..00d3a26 100644 --- a/src/renderer/components/ChatPane.tsx +++ b/src/renderer/components/ChatPane.tsx @@ -41,6 +41,7 @@ interface ChatPaneProps { session: SessionRecord; availableModels: ReadonlyArray; toolingSettings: WorkspaceToolingSettings; + mcpProbingServerIds?: string[]; runtimeTools?: ReadonlyArray; sessionUsage?: SessionUsageState; onSend: (content: string, attachments?: ChatMessageAttachment[], messageMode?: MessageMode) => Promise; @@ -65,6 +66,7 @@ export function ChatPane({ session, availableModels, toolingSettings, + mcpProbingServerIds, runtimeTools, sessionUsage, onSend, @@ -143,6 +145,8 @@ export function ChatPane({ } return counted.size; }, [approvalTools, effectiveAutoApproved, toolingSettings]); + const isProbingMcp = (mcpProbingServerIds?.length ?? 0) > 0; + const hasApprovalContent = approvalTools.length > 0 || isProbingMcp; useEffect(() => { transcriptRef.current?.scrollTo({ @@ -487,13 +491,14 @@ export function ChatPane({ selection={toolSelection} /> )} - {hasToolCallApproval && onUpdateSessionApprovalSettings && approvalTools.length > 0 && ( + {hasToolCallApproval && onUpdateSessionApprovalSettings && hasApprovalContent && ( @@ -543,13 +548,14 @@ export function ChatPane({ selection={toolSelection} /> )} - {hasToolCallApproval && onUpdateSessionApprovalSettings && approvalTools.length > 0 && ( + {hasToolCallApproval && onUpdateSessionApprovalSettings && hasApprovalContent && ( diff --git a/src/renderer/components/chat/InlinePills.tsx b/src/renderer/components/chat/InlinePills.tsx index 213d3f3..7d7efc8 100644 --- a/src/renderer/components/chat/InlinePills.tsx +++ b/src/renderer/components/chat/InlinePills.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from 'react'; -import { ChevronDown, ChevronRight, Minus, Search, Sparkles } from 'lucide-react'; +import { ChevronDown, ChevronRight, Loader2, Minus, Search, Sparkles } from 'lucide-react'; import { ProviderIcon } from '@renderer/components/ProviderIcons'; import { PopoverToggleRow } from '@renderer/components/ui'; @@ -347,6 +347,7 @@ export function InlineApprovalPill({ effectiveAutoApprovedCount, isOverridden, disabled, + mcpProbingServerIds, onUpdate, }: { approvalTools: ApprovalToolDefinition[]; @@ -355,6 +356,7 @@ export function InlineApprovalPill({ effectiveAutoApprovedCount: number; isOverridden: boolean; disabled: boolean; + mcpProbingServerIds?: string[]; onUpdate: (settings: { autoApprovedToolNames?: string[] }) => void; }){ const [open, setOpen] = useState(false); @@ -362,6 +364,12 @@ export function InlineApprovalPill({ const [expandedGroups, setExpandedGroups] = useState>(new Set()); const ref = useClickOutside(() => { setOpen(false); setSearch(''); }, open); + const probingSet = useMemo( + () => new Set(mcpProbingServerIds ?? []), + [mcpProbingServerIds], + ); + const isProbingAny = probingSet.size > 0; + const groups = useMemo( () => groupApprovalToolsByProvider(approvalTools, toolingSettings), [approvalTools, toolingSettings], @@ -439,6 +447,12 @@ export function InlineApprovalPill({ return 'none'; } + function isGroupProbing(group: ApprovalToolGroup): boolean { + if (group.kind !== 'mcp') return false; + const serverId = group.id.replace(/^mcp:/, ''); + return probingSet.has(serverId); + } + function toggleExpanded(groupId: string) { setExpandedGroups((prev) => { const next = new Set(prev); @@ -472,8 +486,15 @@ export function InlineApprovalPill({ onClick={() => setOpen(!open)} type="button" > - - {effectiveAutoApprovedCount}/{totalItemCount} auto-approved + {isProbingAny ? ( + + ) : ( + + )} + + {effectiveAutoApprovedCount}/{totalItemCount} auto-approved + {isProbingAny && · probing…} + @@ -525,6 +546,7 @@ export function InlineApprovalPill({ const isBuiltin = group.kind === 'builtin'; const isCollapsible = !isBuiltin; const expanded = isBuiltin || isGroupExpanded(group.id); + const probing = isGroupProbing(group); const groupState = isGroupApproved(group); const allApproved = groupState === 'all'; const someApproved = groupState === 'some'; @@ -547,20 +569,30 @@ export function InlineApprovalPill({ role="button" tabIndex={0} > - {group.tools.length > 0 ? ( + {probing ? ( + + ) : group.tools.length > 0 ? ( ) : ( )} {group.label} - - {approvedLabel} - - { e.stopPropagation(); toggleGroup(group); }} - /> + {probing ? ( + + probing… + + ) : ( + + {approvedLabel} + + )} + {!probing && ( + { e.stopPropagation(); toggleGroup(group); }} + /> + )} )}