Skip to main content
Glama
ToolGroupForm.tsx6.68 kB
import { Checkbox } from "@/components/ui/checkbox"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { ChevronDown, ChevronRight, Server } from "lucide-react"; import { useState } from "react"; import { useFormContext, UseFormRegisterReturn } from "react-hook-form"; export function ToolGroupForm({ mcpServers, registerNameField, selectedTools, setSelectedTools, }: { mcpServers: { name: string; tools: { name: string; description: string | undefined; }[]; }[]; registerNameField: () => UseFormRegisterReturn<"name">; selectedTools: Record<string, Record<string, boolean>>; setSelectedTools: React.Dispatch< React.SetStateAction<Record<string, Record<string, boolean>>> >; }) { const { formState: { errors }, } = useFormContext(); const [expandedServers, setExpandedServers] = useState< Record<number, boolean> >({}); return ( <div className="space-y-4 p-4"> <div> <Label className="text-sm text-[var(--color-text-primary)] mb-2 block" htmlFor="new-group-name" > Tool Group Name </Label> <Input placeholder="Enter unique group name" className="bg-background" autoComplete="off" {...registerNameField()} /> <p className="text-xs text-[var(--color-fg-danger)] mt-1 pl-2 h-4"> {typeof errors.name?.message === "string" ? errors.name.message : errors.name?.type === "required" ? "This field is required." : ""} </p> </div> <div> <Label className="text-sm mb-3 block">Select MCP Servers & Tools</Label> <div className="space-y-2"> {mcpServers.map((server, serverIndex) => ( <Collapsible id={`server-${serverIndex}`} key={server.name} open={expandedServers[serverIndex]} > <div className="border border-[var(--color-border-primary)] rounded-lg"> <CollapsibleTrigger className="w-full p-3 flex items-center justify-between text-[var(--color-fg-interactive)] hover:bg-[var(--color-bg-interactive-hover)] hover:text-[var(--color-fg-interactive-hover)] cursor-pointer" onClick={(e) => { if (e.currentTarget !== e.target) { // Prevent toggling if clicking on checkbox return; } setExpandedServers((p) => ({ ...p, [serverIndex]: !p[serverIndex], })); }} > {/* * Here we use `pointer-events-none` to make sure * that the event target is always the CollapsibleTrigger. * This way we can distinguish between clicks on the trigger * and clicks on the checkbox. */} <div className="flex items-center gap-3 pointer-events-none"> {expandedServers[serverIndex] ? ( <ChevronDown className="w-4 h-4" /> ) : ( <ChevronRight className="w-4 h-4" /> )} <Server className="w-4 h-4" /> <span className="font-medium text-sm truncate max-w-[26rem]"> {server.name} </span> </div> <Checkbox checked={ server.tools.length > 0 && server.tools.every( (tool) => selectedTools[server.name]?.[tool.name], ) ? true : server.tools.length > 0 && server.tools.some( (tool) => selectedTools[server.name]?.[tool.name], ) ? "indeterminate" : false } onClick={() => { const newSelected = { ...selectedTools }; const checked = server.tools.every( (tool) => selectedTools[server.name]?.[tool.name], ); server.tools.forEach((tool) => { newSelected[server.name] = { ...newSelected[server.name], [tool.name]: !checked, }; }); setSelectedTools(newSelected); return false; }} /> </CollapsibleTrigger> <CollapsibleContent className="px-6 pb-3"> <div className="space-y-2 pt-2"> {server.tools.map((tool, toolIndex) => ( <div key={toolIndex} className="flex items-center justify-between gap-3 py-2 border-t border-[var(--color-border-primary)] first:border-t-0" > <div> <div className="font-medium text-sm text-[var(--color-text-primary)]"> {tool.name} </div> <div className="text-xs text-[var(--color-fg-info)]"> {tool.description} </div> </div> <Checkbox checked={ selectedTools[server.name]?.[tool.name] || false } onClick={() => { setSelectedTools((p) => ({ ...p, [server.name]: { ...p[server.name], [tool.name]: !selectedTools[server.name]?.[tool.name], }, })); return false; }} /> </div> ))} </div> </CollapsibleContent> </div> </Collapsible> ))} </div> </div> </div> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/TheLunarCompany/lunar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server