From c9460de99f4725bf89cd77e29174b1bb82e51989 Mon Sep 17 00:00:00 2001 From: Tom O'Rourke Date: Sat, 18 Apr 2026 16:16:49 +0100 Subject: [PATCH 1/2] fix(ui): tool selection checkboxes snap back to unchecked on click toolResponseMatchesTool compared tool.mcpServer.name directly against toolResponse.server_name. However, toolResponseToAgentTool parses server_name as a Kubernetes namespace/name ref and stores only the name portion in mcpServer.name (e.g. 'kagent-tool-server' from 'kagent/kagent-tool-server'). The mismatch meant isToolSelected() always returned false even after a tool was added to selectedTools, causing the controlled to reset to unchecked on every re-render immediately after a click. From the user's perspective the checkbox appeared completely unresponsive. Fix: normalise server_name by stripping the namespace prefix before comparing, consistent with how toolResponseToAgentTool already handles the same value. Signed-off-by: Tom O'Rourke --- .../components/onboarding/steps/ToolSelectionStep.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/src/components/onboarding/steps/ToolSelectionStep.tsx b/ui/src/components/onboarding/steps/ToolSelectionStep.tsx index 0ccebc47e..2313f42b6 100644 --- a/ui/src/components/onboarding/steps/ToolSelectionStep.tsx +++ b/ui/src/components/onboarding/steps/ToolSelectionStep.tsx @@ -39,7 +39,16 @@ export function ToolSelectionStep({ if (tool.type === "Agent" && tool.agent) { return false; // Agents don't match ToolResponse objects } else if (tool.type === "McpServer" && tool.mcpServer) { - return tool.mcpServer.name === toolResponse.server_name && + // toolResponseToAgentTool parses server_name as a k8s ref (namespace/name) and stores + // only the name part in mcpServer.name. Normalise server_name the same way before + // comparing, otherwise the checkbox always compares "kagent-tool-server" against + // "kagent/kagent-tool-server" and the controlled snaps back to unchecked + // on every click — making tool selection appear broken. + const serverName = toolResponse.server_name ?? ""; + const normalizedServerName = serverName.includes("/") + ? serverName.split("/").pop()! + : serverName; + return tool.mcpServer.name === normalizedServerName && tool.mcpServer.toolNames.includes(toolResponse.id); } return false; From feb277c2937633f7a02b8d0aaadc5cab88c42333 Mon Sep 17 00:00:00 2001 From: Tom O'Rourke Date: Sat, 18 Apr 2026 16:34:57 +0100 Subject: [PATCH 2/2] Use serverNamesMatch helper instead of ad-hoc normalization Replace inline includes('/')/split logic with the existing serverNamesMatch() from toolUtils.ts, which handles k8s refs via isValidRef() and guards against empty names. Also fixes British spelling (Normalise -> Normalize) per codebase convention. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Tom O'Rourke --- .../onboarding/steps/ToolSelectionStep.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ui/src/components/onboarding/steps/ToolSelectionStep.tsx b/ui/src/components/onboarding/steps/ToolSelectionStep.tsx index 2313f42b6..f04fff944 100644 --- a/ui/src/components/onboarding/steps/ToolSelectionStep.tsx +++ b/ui/src/components/onboarding/steps/ToolSelectionStep.tsx @@ -7,7 +7,7 @@ import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Info, ChevronDown, ChevronRight, FunctionSquare, Search } from 'lucide-react'; import { LoadingState } from "@/components/LoadingState"; import { ErrorState } from "@/components/ErrorState"; -import { getToolResponseDisplayName, getToolResponseDescription, getToolResponseIdentifier, getToolResponseCategory, toolResponseToAgentTool } from "@/lib/toolUtils"; +import { getToolResponseDisplayName, getToolResponseDescription, getToolResponseIdentifier, getToolResponseCategory, toolResponseToAgentTool, serverNamesMatch } from "@/lib/toolUtils"; import type { Tool, ToolsResponse } from "@/types"; import { Input } from "@/components/ui/input"; @@ -39,16 +39,11 @@ export function ToolSelectionStep({ if (tool.type === "Agent" && tool.agent) { return false; // Agents don't match ToolResponse objects } else if (tool.type === "McpServer" && tool.mcpServer) { - // toolResponseToAgentTool parses server_name as a k8s ref (namespace/name) and stores - // only the name part in mcpServer.name. Normalise server_name the same way before - // comparing, otherwise the checkbox always compares "kagent-tool-server" against - // "kagent/kagent-tool-server" and the controlled snaps back to unchecked - // on every click — making tool selection appear broken. - const serverName = toolResponse.server_name ?? ""; - const normalizedServerName = serverName.includes("/") - ? serverName.split("/").pop()! - : serverName; - return tool.mcpServer.name === normalizedServerName && + // toolResponseToAgentTool stores only the name part of a k8s ref in + // mcpServer.name, so normalize both sides via serverNamesMatch to avoid + // "kagent-tool-server" vs "kagent/kagent-tool-server" mismatches that + // cause the controlled to snap back to unchecked on every click. + return serverNamesMatch(tool.mcpServer.name, toolResponse.server_name ?? "") && tool.mcpServer.toolNames.includes(toolResponse.id); } return false;