{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "smithery-connections", "title": "Smithery Connections", "description": "A component for managing and displaying MCP server connections via Smithery.", "dependencies": [ "@ai-sdk/mcp", "@smithery/api", "@tanstack/react-query", "ai", "lucide-react", "react-hook-form", "tokenx", "shiki" ], "registryDependencies": [ "avatar", "badge", "button", "card", "combobox", "dialog", "field", "input", "item", "select", "separator", "switch", "tabs", "textarea", "toggle" ], "files": [ { "path": "registry/new-york/hooks/use-debounce.ts", "content": "import { useEffect, useState } from \"react\";\n\nexport function useDebounce(value: T, delay: number): T {\n\tconst [debouncedValue, setDebouncedValue] = useState(value);\n\n\tuseEffect(() => {\n\t\tconst handler = setTimeout(() => {\n\t\t\tsetDebouncedValue(value);\n\t\t}, delay);\n\n\t\treturn () => {\n\t\t\tclearTimeout(handler);\n\t\t};\n\t}, [value, delay]);\n\n\treturn debouncedValue;\n}\n", "type": "registry:hook", "target": "hooks/use-debounce.ts" }, { "path": "registry/new-york/smithery/query-client-wrapper.tsx", "content": "\"use client\";\n\nimport {\n\tQueryClient,\n\tQueryClientContext,\n\tQueryClientProvider,\n} from \"@tanstack/react-query\";\nimport { type ReactNode, useContext, useState } from \"react\";\n\n// Shared default QueryClient for all Smithery components\nlet sharedQueryClient: QueryClient | null = null;\n\nfunction getSharedQueryClient() {\n\tif (!sharedQueryClient) {\n\t\tsharedQueryClient = new QueryClient({\n\t\t\tdefaultOptions: {\n\t\t\t\tqueries: { staleTime: 60 * 1000 },\n\t\t\t},\n\t\t});\n\t}\n\treturn sharedQueryClient;\n}\n\n/**\n * Wrapper that auto-provides QueryClient if none exists.\n * - If a QueryClientProvider already exists, uses that client (user's takes precedence)\n * - If no provider exists, creates a shared client for Smithery components\n */\nexport function WithQueryClient({ children }: { children: ReactNode }) {\n\t// Check if we're already inside a QueryClientProvider\n\tconst existingClient = useContext(QueryClientContext);\n\n\t// Create our own client only if needed (lazy init, stable across renders)\n\tconst [fallbackClient] = useState(() => getSharedQueryClient());\n\n\t// If there's already a client, just render children\n\tif (existingClient) {\n\t\treturn <>{children};\n\t}\n\n\t// Otherwise, provide our own client\n\treturn (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n}\n", "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, { "path": "registry/new-york/smithery/connection-context.tsx", "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\n\n// Context for connection config - consumed by ToolDetailDialog for code generation\nexport interface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nexport const ConnectionConfigContext = createContext(\n\tnull,\n);\n\nexport function useConnectionConfig() {\n\treturn useContext(ConnectionConfigContext);\n}\n", "type": "registry:component", "target": "components/smithery/connection-context.tsx" }, { "path": "registry/new-york/smithery/code-block.tsx", "content": "\"use client\";\n\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport {\n\ttype ComponentProps,\n\tcreateContext,\n\ttype HTMLAttributes,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { type BundledLanguage, codeToHtml, type ShikiTransformer } from \"shiki\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype CodeBlockProps = HTMLAttributes & {\n\tcode: string;\n\tlanguage: BundledLanguage;\n\tshowLineNumbers?: boolean;\n};\n\ntype CodeBlockContextType = {\n\tcode: string;\n};\n\nconst CodeBlockContext = createContext({\n\tcode: \"\",\n});\n\nconst lineNumberTransformer: ShikiTransformer = {\n\tname: \"line-numbers\",\n\tline(node, line) {\n\t\tnode.children.unshift({\n\t\t\ttype: \"element\",\n\t\t\ttagName: \"span\",\n\t\t\tproperties: {\n\t\t\t\tclassName: [\n\t\t\t\t\t\"inline-block\",\n\t\t\t\t\t\"min-w-10\",\n\t\t\t\t\t\"mr-4\",\n\t\t\t\t\t\"text-right\",\n\t\t\t\t\t\"select-none\",\n\t\t\t\t\t\"text-muted-foreground\",\n\t\t\t\t],\n\t\t\t},\n\t\t\tchildren: [{ type: \"text\", value: String(line) }],\n\t\t});\n\t},\n};\n\nexport async function highlightCode(\n\tcode: string,\n\tlanguage: BundledLanguage,\n\tshowLineNumbers = false,\n) {\n\tconst transformers: ShikiTransformer[] = showLineNumbers\n\t\t? [lineNumberTransformer]\n\t\t: [];\n\n\treturn await Promise.all([\n\t\tcodeToHtml(code, {\n\t\t\tlang: language,\n\t\t\ttheme: \"one-light\",\n\t\t\ttransformers,\n\t\t}),\n\t\tcodeToHtml(code, {\n\t\t\tlang: language,\n\t\t\ttheme: \"one-dark-pro\",\n\t\t\ttransformers,\n\t\t}),\n\t]);\n}\n\nexport const CodeBlock = ({\n\tcode,\n\tlanguage,\n\tshowLineNumbers = false,\n\tclassName,\n\tchildren,\n\t...props\n}: CodeBlockProps) => {\n\tconst [html, setHtml] = useState(\"\");\n\tconst [darkHtml, setDarkHtml] = useState(\"\");\n\tconst mounted = useRef(false);\n\n\tuseEffect(() => {\n\t\thighlightCode(code, language, showLineNumbers).then(([light, dark]) => {\n\t\t\tif (!mounted.current) {\n\t\t\t\tsetHtml(light);\n\t\t\t\tsetDarkHtml(dark);\n\t\t\t\tmounted.current = true;\n\t\t\t}\n\t\t});\n\n\t\treturn () => {\n\t\t\tmounted.current = false;\n\t\t};\n\t}, [code, language, showLineNumbers]);\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\tpre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm\"\n\t\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: \"this is needed.\"\n\t\t\t\t\t\tdangerouslySetInnerHTML={{ __html: html }}\n\t\t\t\t\t/>\n\t\t\t\t\tpre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm\"\n\t\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: \"this is needed.\"\n\t\t\t\t\t\tdangerouslySetInnerHTML={{ __html: darkHtml }}\n\t\t\t\t\t/>\n\t\t\t\t\t{children && (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t
\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n};\n\nexport type CodeBlockCopyButtonProps = ComponentProps & {\n\tonCopy?: () => void;\n\tonError?: (error: Error) => void;\n\ttimeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n\tonCopy,\n\tonError,\n\ttimeout = 2000,\n\tchildren,\n\tclassName,\n\t...props\n}: CodeBlockCopyButtonProps) => {\n\tconst [isCopied, setIsCopied] = useState(false);\n\tconst { code } = useContext(CodeBlockContext);\n\n\tconst copyToClipboard = async () => {\n\t\tif (typeof window === \"undefined\" || !navigator?.clipboard?.writeText) {\n\t\t\tonError?.(new Error(\"Clipboard API not available\"));\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(code);\n\t\t\tsetIsCopied(true);\n\t\t\tonCopy?.();\n\t\t\tsetTimeout(() => setIsCopied(false), timeout);\n\t\t} catch (error) {\n\t\t\tonError?.(error as Error);\n\t\t}\n\t};\n\n\tconst Icon = isCopied ? CheckIcon : CopyIcon;\n\n\treturn (\n\t\t\n\t\t\t{children ?? }\n\t\t\n\t);\n};\n", "type": "registry:component", "target": "components/smithery/code-block.tsx" }, { "path": "registry/new-york/smithery/tool-detail-dialog.tsx", "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/registry/new-york/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", "type": "registry:component", "target": "components/smithery/tool-detail-dialog.tsx" }, { "path": "registry/new-york/smithery/tool-card.tsx", "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { ChevronRight } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardHeader } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { ToolDetailDialog } from \"@/registry/new-york/smithery/tool-detail-dialog\";\n\ninterface ToolCardProps {\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolCard({ name, tool, onExecute }: ToolCardProps) {\n\tconst [dialogOpen, setDialogOpen] = useState(false);\n\n\treturn (\n\t\t<>\n\t\t\t setDialogOpen(true)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

{name}

\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\n\t\t\t\n\t\t\n\t);\n}\n", "type": "registry:component", "target": "components/smithery/tool-card.tsx" }, { "path": "registry/new-york/smithery/tools-panel.tsx", "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { Search } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { Field, FieldLabel } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport { ToolCard } from \"@/registry/new-york/smithery/tool-card\";\n\ninterface ToolsPanelProps {\n\ttools: Record;\n\tonExecute?: (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => Promise;\n}\n\nexport function ToolsPanel({ tools, onExecute }: ToolsPanelProps) {\n\tconst [searchQuery, setSearchQuery] = useState(\"\");\n\n\tconst filteredTools = useMemo(() => {\n\t\tif (!searchQuery.trim()) {\n\t\t\treturn Object.entries(tools);\n\t\t}\n\n\t\tconst query = searchQuery.toLowerCase();\n\t\treturn Object.entries(tools).filter(([name, tool]) => {\n\t\t\treturn (\n\t\t\t\tname.toLowerCase().includes(query) ||\n\t\t\t\ttool.description?.toLowerCase().includes(query) ||\n\t\t\t\ttool.type?.toLowerCase().includes(query)\n\t\t\t);\n\t\t});\n\t}, [tools, searchQuery]);\n\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!onExecute) {\n\t\t\tthrow new Error(\"No execute handler provided\");\n\t\t}\n\t\treturn await onExecute(toolName, params);\n\t};\n\n\tconst toolCount = Object.keys(tools).length;\n\tconst filteredCount = filteredTools.length;\n\n\tif (toolCount === 0) {\n\t\treturn (\n\t\t\t
\n\t\t\t\t

No tools available

\n\t\t\t
\n\t\t);\n\t}\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t

Tools

\n\t\t\t\t\t\n\t\t\t\t\t\t{filteredCount === toolCount\n\t\t\t\t\t\t\t? `${toolCount} tool${toolCount === 1 ? \"\" : \"s\"}`\n\t\t\t\t\t\t\t: `${filteredCount} of ${toolCount} tools`}\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tSearch tools\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t setSearchQuery(e.target.value)}\n\t\t\t\t\t\t\tclassName=\"pl-9\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{filteredCount === 0 ? (\n\t\t\t\t\t
\n\t\t\t\t\t\t

No tools match your search

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tTry a different search term\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t) : (\n\t\t\t\t\t
\n\t\t\t\t\t\t{filteredTools.map(([name, tool]) => (\n\t\t\t\t\t\t\t handleExecute(name, params)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n}\n", "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, { "path": "registry/new-york/smithery/server-search.tsx", "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/experimental/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.experimental.connect.mcp.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.experimental.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.experimental.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.experimental.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/server-search.tsx" }, { "path": "registry/new-york/smithery/connections.tsx", "content": "\"use client\";\n\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport Smithery from \"@smithery/api\";\nimport { createConnection } from \"@smithery/api/mcp\";\nimport type { Connection } from \"@smithery/api/resources/experimental/connect/connections.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport type { ToolExecutionOptions } from \"ai\";\nimport { Plus, RefreshCw, Trash2, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { cn } from \"@/lib/utils\";\nimport {\n\tConnectionConfigContext,\n\tuseConnectionConfig,\n} from \"@/registry/new-york/smithery/connection-context\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { ServerSearch } from \"@/registry/new-york/smithery/server-search\";\nimport { ToolsPanel } from \"@/registry/new-york/smithery/tools-panel\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.experimental.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n\nconst ConnectionsListInner = ({\n\ttoken,\n\tnamespace,\n\tdefaultActiveConnectionId,\n\tonActiveConnectionIdChange,\n\tdefaultShowSearchServers = true,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tdefaultActiveConnectionId || null,\n\t);\n\tconst [showSearchServers, setShowSearchServers] = useState(\n\t\tdefaultShowSearchServers || false,\n\t);\n\tconst { data, isLoading, error, refetch, isFetching } = useQuery({\n\t\tqueryKey: [\"connections\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst { connections } =\n\t\t\t\tawait client.experimental.connect.connections.list(namespaceToUse);\n\t\t\treturn { connections, namespace: namespaceToUse };\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tuseEffect(() => {\n\t\tif (data?.connections && !defaultActiveConnectionId) {\n\t\t\tsetActiveConnectionId(data?.connections[0]?.connectionId || null);\n\t\t}\n\t}, [data?.connections, defaultActiveConnectionId]);\n\n\tuseEffect(() => {\n\t\tif (activeConnectionId) {\n\t\t\tonActiveConnectionIdChange(activeConnectionId);\n\t\t}\n\t}, [activeConnectionId, onActiveConnectionIdChange]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

Connections

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\t{showSearchServers ? (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t refetch()}\n\t\t\t\t\t\tdisabled={isFetching}\n\t\t\t\t\t\ttitle=\"Refresh connections\"\n\t\t\t\t\t>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t{showSearchServers && (\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{isLoading &&

Loading...

}\n\t\t\t\t{error && (\n\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t{data.connections.length === 0 && (\n\t\t\t\t\t\t\t

No connections found

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{data.connections.map((connection: Connection) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t setActiveConnectionId(connection.connectionId)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionsList = (props: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => (\n\t\n\t\t\n\t\n);\n\nconst ActiveConnection = ({\n\ttoken,\n\tnamespace,\n\tconnectionId,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tconnectionId: string;\n}) => {\n\tconst [countdown, setCountdown] = useState(null);\n\tconst [hasRedirected, setHasRedirected] = useState(false);\n\n\tconst { data, isLoading, error } = useQuery({\n\t\tqueryKey: [\"connection\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst data = await client.experimental.connect.connections.get(connectionId, {\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\treturn { namespace: namespaceToUse, ...data };\n\t\t},\n\t\t// Poll every 2 seconds when auth_required, stop when connected or error\n\t\trefetchInterval: (query) => {\n\t\t\tconst state = query.state.data?.status?.state;\n\t\t\tif (state === \"auth_required\") {\n\t\t\t\treturn 2000;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\n\t// Start countdown when auth_required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tcountdown === null &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetCountdown(5);\n\t\t}\n\t\t// Reset state when connectionId changes or status is no longer auth_required\n\t\tif (data?.status?.state !== \"auth_required\") {\n\t\t\tsetCountdown(null);\n\t\t\tsetHasRedirected(false);\n\t\t}\n\t}, [data?.status?.state, countdown, hasRedirected]);\n\n\t// Countdown timer\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when countdown reaches 0\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tdata?.status?.authorizationUrl &&\n\t\t\tcountdown === 0 &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetHasRedirected(true);\n\t\t\twindow.open(data.status.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [data?.status, countdown, hasRedirected]);\n\n\tconst clientQuery = useQuery({\n\t\tqueryKey: [\"mcp-client\", token, connectionId, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst namespaceToUse =\n\t\t\t\tnamespace || (await getDefaultNamespace(getSmitheryClient(token)));\n\t\t\tconst { transport } = await createConnection({\n\t\t\t\tclient: getSmitheryClient(token),\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\tconst mcpClient = await createMCPClient({ transport });\n\t\t\treturn mcpClient;\n\t\t},\n\t\tenabled: !!connectionId && data?.status?.state === \"connected\",\n\t});\n\n\tconst toolsQuery = useQuery({\n\t\tqueryKey: [\"tools\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tif (!clientQuery.data) {\n\t\t\t\tthrow new Error(\"Client not available\");\n\t\t\t}\n\t\t\tconst client = clientQuery.data;\n\t\t\treturn await client.tools();\n\t\t},\n\t\tenabled: !!clientQuery.data && data?.status?.state === \"connected\",\n\t});\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!toolsQuery.data) {\n\t\t\tthrow new Error(\"Tools not available\");\n\t\t}\n\t\tconst tool = toolsQuery.data[toolName];\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool ${toolName} not found`);\n\t\t}\n\t\t// The execute method from AI SDK tools expects (params, options)\n\t\t// We're executing tools directly, so provide minimal required options\n\t\tconst options: ToolExecutionOptions = {\n\t\t\ttoolCallId: `manual-${Date.now()}`,\n\t\t\tmessages: [],\n\t\t};\n\t\treturn await tool.execute(params, options);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading connection...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{data.iconUrl && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.title ??\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.name ??\n\t\t\t\t\t\t\t\t\t\t\tdata.name\n\t\t\t\t\t\t\t\t\t\t)?.charAt(0)}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{data.serverInfo?.title ?? data.serverInfo?.name ?? data.name}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.websiteUrl && (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\tView website\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.serverInfo?.description && (\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.description}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\tConnection created:{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t

·

\n\t\t\t\t\t\t\t

Connection ID: {data.connectionId}

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.metadata && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tMetadata\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{JSON.stringify(data.metadata, null, 2)}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data?.status && data.status.state === \"auth_required\" && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Auth required

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t\t\t\t: \"You should be automatically redirected to authenticate. If not, click the link below:\"}\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tOpen authentication window\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading tools...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tError: {toolsQuery.error.message}\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.data && data && (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nconst ConnectionsInner = ({\n\ttoken,\n\tnamespace,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tnull,\n\t);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{activeConnectionId && (\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const Connections = (props: { token: string; namespace?: string }) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/connections.tsx" } ], "type": "registry:block" }