diff --git a/go/api/config/crd/bases/kagent.dev_agents.yaml b/go/api/config/crd/bases/kagent.dev_agents.yaml index 8180fda2d..e705dd1a3 100644 --- a/go/api/config/crd/bases/kagent.dev_agents.yaml +++ b/go/api/config/crd/bases/kagent.dev_agents.yaml @@ -2277,7 +2277,6 @@ spec: rule: '!(has(self.agent) && self.type != ''Agent'')' - message: type.agent must be specified for Agent filter.type rule: '!(!has(self.agent) && self.type == ''Agent'')' - maxItems: 20 type: array type: object status: @@ -10158,7 +10157,6 @@ spec: rule: '!(has(self.agent) && self.type != ''Agent'')' - message: type.agent must be specified for Agent filter.type rule: '!(!has(self.agent) && self.type == ''Agent'')' - maxItems: 20 type: array type: object x-kubernetes-validations: diff --git a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml index 9118e971b..edba208a7 100644 --- a/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml +++ b/go/api/config/crd/bases/kagent.dev_sandboxagents.yaml @@ -7808,7 +7808,6 @@ spec: rule: '!(has(self.agent) && self.type != ''Agent'')' - message: type.agent must be specified for Agent filter.type rule: '!(!has(self.agent) && self.type == ''Agent'')' - maxItems: 20 type: array type: object x-kubernetes-validations: diff --git a/go/api/v1alpha1/agent_types.go b/go/api/v1alpha1/agent_types.go index ffd98b31c..fe6e4b74f 100644 --- a/go/api/v1alpha1/agent_types.go +++ b/go/api/v1alpha1/agent_types.go @@ -40,7 +40,6 @@ type AgentSpec struct { // If not specified, the default value is true. // +optional Stream *bool `json:"stream,omitempty"` - // +kubebuilder:validation:MaxItems=20 Tools []*Tool `json:"tools,omitempty"` // Can either be a reference to the name of a Memory in the same namespace as the referencing Agent, or a reference to the name of a Memory in a different namespace in the form / // +optional diff --git a/go/api/v1alpha2/agent_types.go b/go/api/v1alpha2/agent_types.go index f19b0c3f4..7f3e3c5e5 100644 --- a/go/api/v1alpha2/agent_types.go +++ b/go/api/v1alpha2/agent_types.go @@ -180,7 +180,6 @@ type DeclarativeAgentSpec struct { // If not specified, the default value is false. // +optional Stream bool `json:"stream,omitempty"` - // +kubebuilder:validation:MaxItems=20 Tools []*Tool `json:"tools,omitempty"` // A2AConfig instantiates an A2A server for this agent, // served on the HTTP port of the kagent kubernetes diff --git a/helm/kagent-crds/templates/kagent.dev_agents.yaml b/helm/kagent-crds/templates/kagent.dev_agents.yaml index 8180fda2d..e705dd1a3 100644 --- a/helm/kagent-crds/templates/kagent.dev_agents.yaml +++ b/helm/kagent-crds/templates/kagent.dev_agents.yaml @@ -2277,7 +2277,6 @@ spec: rule: '!(has(self.agent) && self.type != ''Agent'')' - message: type.agent must be specified for Agent filter.type rule: '!(!has(self.agent) && self.type == ''Agent'')' - maxItems: 20 type: array type: object status: @@ -10158,7 +10157,6 @@ spec: rule: '!(has(self.agent) && self.type != ''Agent'')' - message: type.agent must be specified for Agent filter.type rule: '!(!has(self.agent) && self.type == ''Agent'')' - maxItems: 20 type: array type: object x-kubernetes-validations: diff --git a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml index 9118e971b..edba208a7 100644 --- a/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml +++ b/helm/kagent-crds/templates/kagent.dev_sandboxagents.yaml @@ -7808,7 +7808,6 @@ spec: rule: '!(has(self.agent) && self.type != ''Agent'')' - message: type.agent must be specified for Agent filter.type rule: '!(!has(self.agent) && self.type == ''Agent'')' - maxItems: 20 type: array type: object x-kubernetes-validations: diff --git a/ui/src/components/create/SelectToolsDialog.tsx b/ui/src/components/create/SelectToolsDialog.tsx index 544564ed6..a87d15c0e 100644 --- a/ui/src/components/create/SelectToolsDialog.tsx +++ b/ui/src/components/create/SelectToolsDialog.tsx @@ -15,8 +15,7 @@ import { toast } from "sonner"; import KagentLogo from "../kagent-logo"; import { k8sRefUtils } from "@/lib/k8sUtils"; -// Maximum number of tools that can be selected -const MAX_TOOLS_LIMIT = 20; +const TOOLS_WARNING_THRESHOLD = 20; interface SelectToolsDialogProps { open: boolean; @@ -119,7 +118,7 @@ export const SelectToolsDialog: React.FC = ({ open, onOp }, 0); }, [localSelectedTools]); - const isLimitReached = actualSelectedCount >= MAX_TOOLS_LIMIT; + const showToolCountWarning = actualSelectedCount >= TOOLS_WARNING_THRESHOLD; const filteredAvailableItems = useMemo(() => { const searchLower = searchTerm.toLowerCase(); @@ -223,10 +222,6 @@ export const SelectToolsDialog: React.FC = ({ open, onOp return; } - if (actualSelectedCount >= MAX_TOOLS_LIMIT) { - return; - } - let toolToAdd: Tool; if (isAgentResponse(item)) { @@ -427,20 +422,18 @@ export const SelectToolsDialog: React.FC = ({ open, onOp {items.map((item) => { const { displayName, description, identifier, providerText } = getItemDisplayInfo(item); const isSelected = isItemSelected(item); - const isDisabled = !isSelected && isLimitReached; - return (
!isDisabled && !isSelected && handleAddItem(item)} + className={`flex items-center justify-between p-3 pr-2 group min-w-0 ${isSelected ? 'cursor-default' : 'cursor-pointer hover:bg-muted/50'}`} + onClick={() => !isSelected && handleAddItem(item)} >

{highlightMatch(displayName, searchTerm)}

{description &&

{highlightMatch(description, searchTerm)}

} {providerText &&

{highlightMatch(providerText, searchTerm)}

}
- {!isSelected && !isDisabled && ( + {!isSelected && ( @@ -506,17 +499,17 @@ export const SelectToolsDialog: React.FC = ({ open, onOp {/* Right Panel: Selected Tools */}
-

Selected ({actualSelectedCount}/{MAX_TOOLS_LIMIT})

+

Selected ({actualSelectedCount})

- {isLimitReached && actualSelectedCount >= MAX_TOOLS_LIMIT && ( + {showToolCountWarning && (
- Tool limit reached. Deselect a tool to add another. + You have selected {actualSelectedCount} tools. A large number of tools may increase token usage and affect performance.
)} @@ -679,7 +672,7 @@ export const SelectToolsDialog: React.FC = ({ open, onOp
- Select up to {MAX_TOOLS_LIMIT} tools for your agent. + Select tools and agents for your agent.
diff --git a/ui/src/components/create/__tests__/SelectToolsDialog.test.tsx b/ui/src/components/create/__tests__/SelectToolsDialog.test.tsx new file mode 100644 index 000000000..cbd19bc09 --- /dev/null +++ b/ui/src/components/create/__tests__/SelectToolsDialog.test.tsx @@ -0,0 +1,70 @@ +/** + * @jest-environment jsdom + */ +import { describe, expect, it, jest } from "@jest/globals"; +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { SelectToolsDialog } from "@/components/create/SelectToolsDialog"; +import type { AgentResponse, Tool, ToolsResponse } from "@/types"; + +const serverRef = "kagent/kagent-tool-server"; + +const makeTool = (id: string): ToolsResponse => ({ + id, + server_name: serverRef, + description: `Description for ${id}`, + created_at: "2026-01-01T00:00:00Z", + updated_at: "2026-01-01T00:00:00Z", + deleted_at: "", + group_kind: "MCPServer.kagent.dev", +}); + +const makeSelectedTools = (count: number): Tool[] => [ + { + type: "McpServer", + mcpServer: { + name: "kagent-tool-server", + namespace: "kagent", + kind: "MCPServer", + apiGroup: "kagent.dev", + toolNames: Array.from({ length: count }, (_, i) => `tool_${i}`), + }, + }, +]; + +const renderOpenDialog = (selectedTools: Tool[], availableTools: ToolsResponse[]) => { + const props = { + open: false, + onOpenChange: jest.fn(), + availableTools, + selectedTools, + onToolsSelected: jest.fn(), + availableAgents: [] as AgentResponse[], + loadingAgents: false, + currentAgentNamespace: "kagent", + }; + + const view = render(); + view.rerender(); + return props; +}; + +describe("SelectToolsDialog", () => { + it("warns but does not block selection when many tools are selected", async () => { + const user = userEvent.setup(); + const availableTools = Array.from({ length: 21 }, (_, i) => makeTool(`tool_${i}`)); + + renderOpenDialog(makeSelectedTools(20), availableTools); + + expect(screen.getByText("Selected (20)")).toBeInTheDocument(); + expect(screen.getByText(/You have selected 20 tools/i)).toBeInTheDocument(); + expect(screen.queryByText(/Tool limit reached/i)).not.toBeInTheDocument(); + expect(screen.queryByText(/Select up to/i)).not.toBeInTheDocument(); + + await user.click(screen.getByText("tool_20")); + + expect(screen.getByText("Selected (21)")).toBeInTheDocument(); + expect(screen.getByText(/You have selected 21 tools/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Save Selection \(21\)/i })).toBeInTheDocument(); + }); +});